2 * Copyright 2001-2013 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
= (B_LIGHTEN_1_TINT
+ B_LIGHTEN_1_TINT
+ B_NO_TINT
) / 3.0;
32 // map control key shortcuts to drawable Unicode characters
33 // cf. http://unicode.org/charts/PDF/U2190.pdf
34 const char *kUTF8ControlMap
[] = {
36 "\xe2\x86\xb8", /* B_HOME U+21B8 */
41 NULL
, /* B_BACKSPACE */
42 "\xe2\x86\xb9", /* B_TAB U+21B9 */
43 "\xe2\x86\xb5", /* B_ENTER, U+21B5 */
44 //"\xe2\x8f\x8e", /* B_ENTER, U+23CE it's the official one */
46 NULL
, /* B_PAGE_DOWN */
48 NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
,
49 NULL
, NULL
, NULL
, NULL
,
50 "\xe2\x86\x90", /* B_LEFT_ARROW */
51 "\xe2\x86\x92", /* B_RIGHT_ARROW */
52 "\xe2\x86\x91", /* B_UP_ARROW */
53 "\xe2\x86\x93", /* B_DOWN_ARROW */
56 using BPrivate::MenuPrivate
;
58 BMenuItem::BMenuItem(const char* label
, BMessage
* message
, char shortcut
,
63 fLabel
= strdup(label
);
67 fShortcutChar
= shortcut
;
70 fModifiers
= modifiers
| B_COMMAND_KEY
;
76 BMenuItem::BMenuItem(BMenu
* menu
, BMessage
* message
)
84 BMenuItem::BMenuItem(BMessage
* data
)
88 if (data
->HasString("_label")) {
91 data
->FindString("_label", &string
);
96 if (data
->FindBool("_disable", &disable
) == B_OK
)
100 if (data
->FindBool("_marked", &marked
) == B_OK
)
104 if (data
->FindInt32("_user_trig", &userTrigger
) == B_OK
)
105 SetTrigger(userTrigger
);
107 if (data
->HasInt32("_shortcut")) {
108 int32 shortcut
, mods
;
110 data
->FindInt32("_shortcut", &shortcut
);
111 data
->FindInt32("_mods", &mods
);
113 SetShortcut(shortcut
, mods
);
116 if (data
->HasMessage("_msg")) {
117 BMessage
*msg
= new BMessage
;
118 data
->FindMessage("_msg", msg
);
123 if (data
->FindMessage("_submenu", &subMessage
) == B_OK
) {
124 BArchivable
* object
= instantiate_object(&subMessage
);
125 if (object
!= NULL
) {
126 BMenu
* menu
= dynamic_cast<BMenu
*>(object
);
135 BMenuItem::Instantiate(BMessage
* data
)
137 if (validate_instantiation(data
, "BMenuItem"))
138 return new BMenuItem(data
);
145 BMenuItem::Archive(BMessage
* data
, bool deep
) const
147 status_t ret
= BArchivable::Archive(data
, deep
);
149 if (ret
== B_OK
&& fLabel
)
150 ret
= data
->AddString("_label", Label());
152 if (ret
== B_OK
&& !IsEnabled())
153 ret
= data
->AddBool("_disable", true);
155 if (ret
== B_OK
&& IsMarked())
156 ret
= data
->AddBool("_marked", true);
158 if (ret
== B_OK
&& fUserTrigger
)
159 ret
= data
->AddInt32("_user_trig", fUserTrigger
);
161 if (ret
== B_OK
&& fShortcutChar
) {
162 ret
= data
->AddInt32("_shortcut", fShortcutChar
);
164 ret
= data
->AddInt32("_mods", fModifiers
);
167 if (ret
== B_OK
&& Message())
168 ret
= data
->AddMessage("_msg", Message());
170 if (ret
== B_OK
&& deep
&& fSubmenu
) {
172 if (fSubmenu
->Archive(&submenu
, true) == B_OK
)
173 ret
= data
->AddMessage("_submenu", &submenu
);
180 BMenuItem::~BMenuItem()
188 BMenuItem::SetLabel(const char* string
)
190 if (fLabel
!= NULL
) {
196 fLabel
= strdup(string
);
198 if (fSuper
!= NULL
) {
199 fSuper
->InvalidateLayout();
201 if (fSuper
->LockLooper()) {
202 fSuper
->Invalidate();
203 fSuper
->UnlockLooper();
210 BMenuItem::SetEnabled(bool enable
)
212 if (fEnabled
== enable
)
217 if (fSubmenu
!= NULL
)
218 fSubmenu
->SetEnabled(enable
);
220 BMenu
* menu
= fSuper
;
221 if (menu
!= NULL
&& menu
->LockLooper()) {
222 menu
->Invalidate(fBounds
);
223 menu
->UnlockLooper();
229 BMenuItem::SetMarked(bool mark
)
233 if (mark
&& fSuper
!= NULL
) {
234 MenuPrivate
priv(fSuper
);
235 priv
.ItemMarked(this);
241 BMenuItem::SetTrigger(char trigger
)
243 fUserTrigger
= trigger
;
245 // try uppercase letters first
247 const char* pos
= strchr(Label(), toupper(trigger
));
248 trigger
= tolower(trigger
);
251 // take lowercase, too
252 pos
= strchr(Label(), trigger
);
256 fTriggerIndex
= UTF8CountChars(Label(), pos
- Label());
264 fSuper
->InvalidateLayout();
269 BMenuItem::SetShortcut(char shortcut
, uint32 modifiers
)
271 if (fShortcutChar
!= 0 && (fModifiers
& B_COMMAND_KEY
) != 0
272 && fWindow
!= NULL
) {
273 fWindow
->RemoveShortcut(fShortcutChar
, fModifiers
);
276 fShortcutChar
= shortcut
;
279 fModifiers
= modifiers
| B_COMMAND_KEY
;
283 if (fShortcutChar
!= 0 && (fModifiers
& B_COMMAND_KEY
) && fWindow
)
284 fWindow
->AddShortcut(fShortcutChar
, fModifiers
, this);
287 fSuper
->InvalidateLayout();
289 if (fSuper
->LockLooper()) {
290 fSuper
->Invalidate();
291 fSuper
->UnlockLooper();
298 BMenuItem::Label() const
305 BMenuItem::IsEnabled() const
308 return fSubmenu
->IsEnabled();
313 return fSuper
!= NULL
? fSuper
->IsEnabled() : true;
318 BMenuItem::IsMarked() const
325 BMenuItem::Trigger() const
332 BMenuItem::Shortcut(uint32
* modifiers
) const
335 *modifiers
= fModifiers
;
337 return fShortcutChar
;
342 BMenuItem::Submenu() const
349 BMenuItem::Menu() const
356 BMenuItem::Frame() const
363 BMenuItem::GetContentSize(float* _width
, float* _height
)
365 // TODO: Get rid of this. BMenu should handle this
366 // automatically. Maybe it's not even needed, since our
367 // BFont::Height() caches the value locally
368 MenuPrivate(fSuper
).CacheFontInfo();
370 fCachedWidth
= fSuper
->StringWidth(fLabel
);
373 *_width
= (float)ceil(fCachedWidth
);
375 *_height
= MenuPrivate(fSuper
).FontHeight();
380 BMenuItem::TruncateLabel(float maxWidth
, char* newLabel
)
383 fSuper
->GetFont(&font
);
385 BString
string(fLabel
);
387 font
.TruncateString(&string
, B_TRUNCATE_MIDDLE
, maxWidth
);
389 string
.CopyInto(newLabel
, 0, string
.Length());
390 newLabel
[string
.Length()] = '\0';
395 BMenuItem::DrawContent()
397 MenuPrivate
menuPrivate(fSuper
);
398 menuPrivate
.CacheFontInfo();
400 fSuper
->MovePenBy(0, menuPrivate
.Ascent());
401 BPoint lineStart
= fSuper
->PenLocation();
403 fSuper
->SetDrawingMode(B_OP_OVER
);
407 GetContentSize(&labelWidth
, &labelHeight
);
409 const BRect
& padding
= menuPrivate
.Padding();
410 float maxContentWidth
= fSuper
->MaxContentWidth();
411 float frameWidth
= maxContentWidth
> 0 ? maxContentWidth
412 : fSuper
->Frame().Width() - padding
.left
- padding
.right
;
414 if (roundf(frameWidth
) >= roundf(labelWidth
))
415 fSuper
->DrawString(fLabel
);
417 // truncate label to fit
418 char* truncatedLabel
= new char[strlen(fLabel
) + 4];
419 TruncateLabel(frameWidth
, truncatedLabel
);
420 fSuper
->DrawString(truncatedLabel
);
421 delete[] truncatedLabel
;
424 if (fSuper
->AreTriggersEnabled() && fTriggerIndex
!= -1) {
425 float escapements
[fTriggerIndex
+ 1];
427 fSuper
->GetFont(&font
);
429 font
.GetEscapements(fLabel
, fTriggerIndex
+ 1, escapements
);
431 for (int32 i
= 0; i
< fTriggerIndex
; i
++)
432 lineStart
.x
+= escapements
[i
] * font
.Size();
437 BPoint
lineEnd(lineStart
);
438 lineEnd
.x
+= escapements
[fTriggerIndex
] * font
.Size();
440 fSuper
->StrokeLine(lineStart
, lineEnd
);
448 const rgb_color lowColor
= fSuper
->LowColor();
449 const rgb_color highColor
= fSuper
->HighColor();
451 bool enabled
= IsEnabled();
452 bool selected
= IsSelected();
453 bool activated
= selected
&& (enabled
|| Submenu() != NULL
);
457 fSuper
->SetLowColor(ui_color(B_MENU_SELECTED_BACKGROUND_COLOR
));
458 // fill in the background
460 be_control_look
->DrawMenuItemBackground(fSuper
, rect
, Frame(),
461 fSuper
->LowColor(), BControlLook::B_ACTIVATED
);
463 fSuper
->SetLowColor(ui_color(B_MENU_BACKGROUND_COLOR
));
466 if (activated
&& enabled
)
467 fSuper
->SetHighColor(ui_color(B_MENU_SELECTED_ITEM_TEXT_COLOR
));
469 fSuper
->SetHighColor(ui_color(B_MENU_ITEM_TEXT_COLOR
));
471 rgb_color bgColor
= fSuper
->LowColor();
472 if (bgColor
.red
+ bgColor
.green
+ bgColor
.blue
> 128 * 3)
473 fSuper
->SetHighColor(tint_color(bgColor
, B_DISABLED_LABEL_TINT
));
475 fSuper
->SetHighColor(tint_color(bgColor
, B_LIGHTEN_2_TINT
));
479 fSuper
->MovePenTo(ContentLocation());
482 // draw extra symbols
483 const menu_layout layout
= MenuPrivate(fSuper
).Layout();
484 if (layout
== B_ITEMS_IN_COLUMN
) {
489 _DrawShortcutSymbol();
492 _DrawSubmenuSymbol();
495 // restore the parent menu's low color and high color
496 fSuper
->SetLowColor(lowColor
);
497 fSuper
->SetHighColor(highColor
);
502 BMenuItem::Highlight(bool highlight
)
504 fSuper
->Invalidate(Frame());
509 BMenuItem::IsSelected() const
516 BMenuItem::ContentLocation() const
518 const BRect
& padding
= MenuPrivate(fSuper
).Padding();
520 return BPoint(fBounds
.left
+ padding
.left
, fBounds
.top
+ padding
.top
);
524 void BMenuItem::_ReservedMenuItem1() {}
525 void BMenuItem::_ReservedMenuItem2() {}
526 void BMenuItem::_ReservedMenuItem3() {}
527 void BMenuItem::_ReservedMenuItem4() {}
530 BMenuItem::BMenuItem(const BMenuItem
&)
536 BMenuItem::operator=(const BMenuItem
&)
543 BMenuItem::_InitData()
562 BMenuItem::_InitMenuData(BMenu
* menu
)
566 MenuPrivate(fSubmenu
).SetSuperItem(this);
568 BMenuItem
* item
= menu
->FindMarked();
570 if (menu
->IsRadioMode() && menu
->IsLabelFromMarked() && item
!= NULL
)
571 SetLabel(item
->Label());
573 SetLabel(menu
->Name());
578 BMenuItem::Install(BWindow
* window
)
580 if (fSubmenu
!= NULL
)
581 MenuPrivate(fSubmenu
).Install(window
);
585 if (fShortcutChar
!= 0 && (fModifiers
& B_COMMAND_KEY
) && fWindow
)
586 window
->AddShortcut(fShortcutChar
, fModifiers
, this);
588 if (!Messenger().IsValid())
594 BMenuItem::Invoke(BMessage
* message
)
599 if (fSuper
->IsRadioMode())
603 uint32 kind
= InvokeKind(¬ify
);
605 BMessage
clone(kind
);
606 status_t err
= B_BAD_VALUE
;
608 if (message
== NULL
&& !notify
)
611 if (message
== NULL
) {
612 if (!fSuper
->IsWatched())
617 clone
.AddInt32("index", fSuper
->IndexOf(this));
618 clone
.AddInt64("when", (int64
)system_time());
619 clone
.AddPointer("source", this);
620 clone
.AddMessenger("be:sender", BMessenger(fSuper
));
623 err
= BInvoker::Invoke(&clone
);
625 // TODO: assynchronous messaging
626 // SendNotices(kind, &clone);
633 BMenuItem::Uninstall()
635 if (fSubmenu
!= NULL
)
636 MenuPrivate(fSubmenu
).Uninstall();
638 if (Target() == fWindow
)
639 SetTarget(BMessenger());
641 if (fShortcutChar
!= 0 && (fModifiers
& B_COMMAND_KEY
) != 0
642 && fWindow
!= NULL
) {
643 fWindow
->RemoveShortcut(fShortcutChar
, fModifiers
);
651 BMenuItem::SetSuper(BMenu
* super
)
653 if (fSuper
!= NULL
&& super
!= NULL
) {
654 debugger("Error - can't add menu or menu item to more than 1 container"
655 " (either menu or menubar).");
658 if (fSubmenu
!= NULL
)
659 MenuPrivate(fSubmenu
).SetSuper(super
);
666 BMenuItem::Select(bool selected
)
668 if (fSelected
== selected
)
671 if (Submenu() != NULL
|| IsEnabled()) {
672 fSelected
= selected
;
679 BMenuItem::_DrawMarkSymbol()
685 MenuPrivate(fSuper
).GetItemMargins(&leftMargin
, NULL
, NULL
, NULL
);
686 r
.right
= r
.left
+ leftMargin
- 3;
689 BPoint
center(floorf((r
.left
+ r
.right
) / 2.0),
690 floorf((r
.top
+ r
.bottom
) / 2.0));
692 float size
= min_c(r
.Height() - 2, r
.Width());
693 r
.top
= floorf(center
.y
- size
/ 2 + 0.5);
694 r
.bottom
= floorf(center
.y
+ size
/ 2 + 0.5);
695 r
.left
= floorf(center
.x
- size
/ 2 + 0.5);
696 r
.right
= floorf(center
.x
+ size
/ 2 + 0.5);
702 arrowShape
.MoveTo(BPoint(center
.x
- size
, center
.y
- size
* 0.25));
703 arrowShape
.LineTo(BPoint(center
.x
- size
* 0.25, center
.y
+ size
));
704 arrowShape
.LineTo(BPoint(center
.x
+ size
, center
.y
- size
));
706 fSuper
->SetDrawingMode(B_OP_OVER
);
707 fSuper
->SetPenSize(2.0);
708 // NOTE: StrokeShape() offsets the shape by the current pen position,
709 // it is not documented in the BeBook, but it is true!
710 fSuper
->MovePenTo(B_ORIGIN
);
711 fSuper
->StrokeShape(&arrowShape
);
718 BMenuItem::_DrawShortcutSymbol()
720 BMenu
* menu
= fSuper
;
722 menu
->GetFont(&font
);
723 BPoint where
= ContentLocation();
724 where
.x
= fBounds
.right
- font
.Size();
727 where
.x
-= fBounds
.Height() - 3;
729 const float ascent
= MenuPrivate(fSuper
).Ascent();
730 if (fShortcutChar
< B_SPACE
&& kUTF8ControlMap
[(int)fShortcutChar
])
731 _DrawControlChar(fShortcutChar
, where
+ BPoint(0, ascent
));
733 fSuper
->DrawChar(fShortcutChar
, where
+ BPoint(0, ascent
));
735 where
.y
+= (fBounds
.Height() - 11) / 2 - 1;
738 // TODO: It would be nice to draw these taking into account the text (low)
740 if (fModifiers
& B_COMMAND_KEY
) {
741 const BBitmap
*command
= MenuPrivate::MenuItemCommand();
742 const BRect
&rect
= command
->Bounds();
743 where
.x
-= rect
.Width() + 1;
744 fSuper
->DrawBitmap(command
, where
);
747 if (fModifiers
& B_CONTROL_KEY
) {
748 const BBitmap
*control
= MenuPrivate::MenuItemControl();
749 const BRect
&rect
= control
->Bounds();
750 where
.x
-= rect
.Width() + 1;
751 fSuper
->DrawBitmap(control
, where
);
754 if (fModifiers
& B_OPTION_KEY
) {
755 const BBitmap
*option
= MenuPrivate::MenuItemOption();
756 const BRect
&rect
= option
->Bounds();
757 where
.x
-= rect
.Width() + 1;
758 fSuper
->DrawBitmap(option
, where
);
761 if (fModifiers
& B_SHIFT_KEY
) {
762 const BBitmap
*shift
= MenuPrivate::MenuItemShift();
763 const BRect
&rect
= shift
->Bounds();
764 where
.x
-= rect
.Width() + 1;
765 fSuper
->DrawBitmap(shift
, where
);
771 BMenuItem::_DrawSubmenuSymbol()
777 MenuPrivate(fSuper
).GetItemMargins(NULL
, NULL
, &rightMargin
, NULL
);
778 r
.left
= r
.right
- rightMargin
+ 3;
781 BPoint
center(floorf((r
.left
+ r
.right
) / 2.0),
782 floorf((r
.top
+ r
.bottom
) / 2.0));
784 float size
= min_c(r
.Height() - 2, r
.Width());
785 r
.top
= floorf(center
.y
- size
/ 2 + 0.5);
786 r
.bottom
= floorf(center
.y
+ size
/ 2 + 0.5);
787 r
.left
= floorf(center
.x
- size
/ 2 + 0.5);
788 r
.right
= floorf(center
.x
+ size
/ 2 + 0.5);
794 float hSize
= size
* 0.7;
795 arrowShape
.MoveTo(BPoint(center
.x
- hSize
, center
.y
- size
));
796 arrowShape
.LineTo(BPoint(center
.x
+ hSize
, center
.y
));
797 arrowShape
.LineTo(BPoint(center
.x
- hSize
, center
.y
+ size
));
799 fSuper
->SetDrawingMode(B_OP_OVER
);
800 fSuper
->SetPenSize(ceilf(size
* 0.4));
801 // NOTE: StrokeShape() offsets the shape by the current pen position,
802 // it is not documented in the BeBook, but it is true!
803 fSuper
->MovePenTo(B_ORIGIN
);
804 fSuper
->StrokeShape(&arrowShape
);
811 BMenuItem::_DrawControlChar(char shortcut
, BPoint where
)
813 // TODO: If needed, take another font for the control characters
814 // (or have font overlays in the app_server!)
815 const char* symbol
= " ";
816 if (kUTF8ControlMap
[(int)fShortcutChar
])
817 symbol
= kUTF8ControlMap
[(int)fShortcutChar
];
819 fSuper
->DrawString(symbol
, where
);
824 BMenuItem::SetAutomaticTrigger(int32 index
, uint32 trigger
)
826 fTriggerIndex
= index
;