btrfs: Attempt to fix GCC2 build.
[haiku.git] / src / kits / interface / MenuItem.cpp
blobc28ce3f73291c0370efac47c7afab5189a076e0e
1 /*
2 * Copyright 2001-2015 Haiku, Inc. All rights reserved.
3 * Distributed under the terms of the MIT License.
5 * Authors:
6 * Stefano Ceccherini, stefano.ceccherini@gmail.com
7 * Marc Flerackers, mflerackers@androme.be
8 * Bill Hayden, haydentech@users.sourceforge.net
9 * Olivier Milla
10 * John Scipione, jscipione@gmail.com
14 #include <ctype.h>
15 #include <stdlib.h>
16 #include <string.h>
18 #include <Bitmap.h>
19 #include <ControlLook.h>
20 #include <MenuItem.h>
21 #include <Shape.h>
22 #include <String.h>
23 #include <Window.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[] = {
36 NULL,
37 "\xe2\x86\xb8", /* B_HOME U+21B8 */
38 NULL, NULL,
39 NULL, /* B_END */
40 NULL, /* B_INSERT */
41 NULL, NULL,
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 */
46 NULL, /* B_PAGE_UP */
47 NULL, /* B_PAGE_DOWN */
48 NULL, NULL, NULL,
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,
61 uint32 modifiers)
63 _InitData();
64 if (label != NULL)
65 fLabel = strdup(label);
67 SetMessage(message);
69 fShortcutChar = shortcut;
71 if (shortcut != 0)
72 fModifiers = modifiers | B_COMMAND_KEY;
73 else
74 fModifiers = 0;
78 BMenuItem::BMenuItem(BMenu* menu, BMessage* message)
80 _InitData();
81 SetMessage(message);
82 _InitMenuData(menu);
86 BMenuItem::BMenuItem(BMessage* data)
88 _InitData();
90 if (data->HasString("_label")) {
91 const char* string;
93 data->FindString("_label", &string);
94 SetLabel(string);
97 bool disable;
98 if (data->FindBool("_disable", &disable) == B_OK)
99 SetEnabled(!disable);
101 bool marked;
102 if (data->FindBool("_marked", &marked) == B_OK)
103 SetMarked(marked);
105 int32 userTrigger;
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);
121 SetMessage(message);
124 BMessage subMessage;
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);
129 if (menu != NULL)
130 _InitMenuData(menu);
136 BArchivable*
137 BMenuItem::Instantiate(BMessage* data)
139 if (validate_instantiation(data, "BMenuItem"))
140 return new BMenuItem(data);
142 return NULL;
146 status_t
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);
165 if (status == B_OK)
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) {
173 BMessage submenu;
174 if (fSubmenu->Archive(&submenu, true) == B_OK)
175 status = data->AddMessage("_submenu", &submenu);
178 return status;
182 BMenuItem::~BMenuItem()
184 free(fLabel);
185 delete fSubmenu;
189 void
190 BMenuItem::SetLabel(const char* string)
192 if (fLabel != NULL) {
193 free(fLabel);
194 fLabel = NULL;
197 if (string != NULL)
198 fLabel = strdup(string);
200 if (fSuper != NULL) {
201 fSuper->InvalidateLayout();
203 if (fSuper->LockLooper()) {
204 fSuper->Invalidate();
205 fSuper->UnlockLooper();
211 void
212 BMenuItem::SetEnabled(bool enable)
214 if (fEnabled == enable)
215 return;
217 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();
230 void
231 BMenuItem::SetMarked(bool mark)
233 fMark = mark;
235 if (mark && fSuper != NULL) {
236 MenuPrivate priv(fSuper);
237 priv.ItemMarked(this);
242 void
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);
252 if (pos == NULL) {
253 // take lowercase, too
254 pos = strchr(Label(), trigger);
257 if (pos != NULL) {
258 fTriggerIndex = UTF8CountChars(Label(), pos - Label());
259 fTrigger = trigger;
260 } else {
261 fTrigger = 0;
262 fTriggerIndex = -1;
265 if (fSuper != NULL)
266 fSuper->InvalidateLayout();
270 void
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;
280 if (shortcut != 0)
281 fModifiers = modifiers | B_COMMAND_KEY;
282 else
283 fModifiers = 0;
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();
299 const char*
300 BMenuItem::Label() const
302 return fLabel;
306 bool
307 BMenuItem::IsEnabled() const
309 if (fSubmenu)
310 return fSubmenu->IsEnabled();
312 if (!fEnabled)
313 return false;
315 return fSuper != NULL ? fSuper->IsEnabled() : true;
319 bool
320 BMenuItem::IsMarked() const
322 return fMark;
326 char
327 BMenuItem::Trigger() const
329 return fUserTrigger;
333 char
334 BMenuItem::Shortcut(uint32* modifiers) const
336 if (modifiers)
337 *modifiers = fModifiers;
339 return fShortcutChar;
343 BMenu*
344 BMenuItem::Submenu() const
346 return fSubmenu;
350 BMenu*
351 BMenuItem::Menu() const
353 return fSuper;
357 BRect
358 BMenuItem::Frame() const
360 return fBounds;
364 void
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);
374 if (_width)
375 *_width = (float)ceil(fCachedWidth);
376 if (_height)
377 *_height = MenuPrivate(fSuper).FontHeight();
381 void
382 BMenuItem::TruncateLabel(float maxWidth, char* newLabel)
384 BFont font;
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';
396 void
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);
407 float labelWidth;
408 float labelHeight;
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);
418 else {
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];
428 BFont font;
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();
436 lineStart.x--;
437 lineStart.y++;
439 BPoint lineEnd(lineStart);
440 lineEnd.x += escapements[fTriggerIndex] * font.Size();
442 fSuper->StrokeLine(lineStart, lineEnd);
447 void
448 BMenuItem::Draw()
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);
457 // set low color
458 if (activated) {
459 fSuper->SetLowColor(ui_color(B_MENU_SELECTED_BACKGROUND_COLOR));
460 // fill in the background
461 BRect rect(Frame());
462 be_control_look->DrawMenuItemBackground(fSuper, rect, Frame(),
463 fSuper->LowColor(), BControlLook::B_ACTIVATED);
464 } else
465 fSuper->SetLowColor(ui_color(B_MENU_BACKGROUND_COLOR));
467 // set high color
468 if (activated && enabled)
469 fSuper->SetHighColor(ui_color(B_MENU_SELECTED_ITEM_TEXT_COLOR));
470 else if (enabled)
471 fSuper->SetHighColor(ui_color(B_MENU_ITEM_TEXT_COLOR));
472 else {
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));
476 else
477 fSuper->SetHighColor(tint_color(bgColor, B_LIGHTEN_2_TINT));
480 // draw content
481 fSuper->MovePenTo(ContentLocation());
482 DrawContent();
484 // draw extra symbols
485 const menu_layout layout = MenuPrivate(fSuper).Layout();
486 if (layout == B_ITEMS_IN_COLUMN) {
487 if (IsMarked())
488 _DrawMarkSymbol();
490 if (fShortcutChar)
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);
503 void
504 BMenuItem::Highlight(bool highlight)
506 fSuper->Invalidate(Frame());
510 bool
511 BMenuItem::IsSelected() const
513 return fSelected;
517 BPoint
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 &)
537 BMenuItem&
538 BMenuItem::operator=(const BMenuItem &)
540 return *this;
544 void
545 BMenuItem::_InitData()
547 fLabel = NULL;
548 fSubmenu = NULL;
549 fWindow = NULL;
550 fSuper = NULL;
551 fModifiers = 0;
552 fCachedWidth = 0;
553 fTriggerIndex = -1;
554 fUserTrigger = 0;
555 fTrigger = 0;
556 fShortcutChar = 0;
557 fMark = false;
558 fEnabled = true;
559 fSelected = false;
563 void
564 BMenuItem::_InitMenuData(BMenu* menu)
566 fSubmenu = menu;
568 MenuPrivate(fSubmenu).SetSuperItem(this);
570 BMenuItem* item = menu->FindMarked();
572 if (menu->IsRadioMode() && menu->IsLabelFromMarked() && item != NULL)
573 SetLabel(item->Label());
574 else
575 SetLabel(menu->Name());
579 void
580 BMenuItem::Install(BWindow* window)
582 if (fSubmenu != NULL)
583 MenuPrivate(fSubmenu).Install(window);
585 fWindow = window;
587 if (fShortcutChar != 0 && (fModifiers & B_COMMAND_KEY) && fWindow)
588 window->AddShortcut(fShortcutChar, fModifiers, this);
590 if (!Messenger().IsValid())
591 SetTarget(window);
595 status_t
596 BMenuItem::Invoke(BMessage* message)
598 if (!IsEnabled())
599 return B_ERROR;
601 if (fSuper->IsRadioMode())
602 SetMarked(true);
604 bool notify = false;
605 uint32 kind = InvokeKind(&notify);
607 BMessage clone(kind);
608 status_t err = B_BAD_VALUE;
610 if (message == NULL && !notify)
611 message = Message();
613 if (message == NULL) {
614 if (!fSuper->IsWatched())
615 return err;
616 } else
617 clone = *message;
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));
624 if (message != NULL)
625 err = BInvoker::Invoke(&clone);
627 // TODO: assynchronous messaging
628 // SendNotices(kind, &clone);
630 return err;
634 void
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);
648 fWindow = NULL;
652 void
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);
663 fSuper = super;
667 void
668 BMenuItem::Select(bool selected)
670 if (fSelected == selected)
671 return;
673 if (Submenu() != NULL || IsEnabled()) {
674 fSelected = selected;
675 Highlight(selected);
680 void
681 BMenuItem::_DrawMarkSymbol()
683 fSuper->PushState();
685 BRect r(fBounds);
686 float leftMargin;
687 MenuPrivate(fSuper).GetItemMargins(&leftMargin, NULL, NULL, NULL);
688 float gap = leftMargin / 4;
689 r.right = r.left + leftMargin - gap;
690 r.left += gap / 3;
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);
701 BShape arrowShape;
702 center.x += 0.5;
703 center.y += 0.5;
704 size *= 0.3;
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);
716 fSuper->PopState();
720 void
721 BMenuItem::_DrawShortcutSymbol()
723 BMenu* menu = fSuper;
724 BFont font;
725 menu->GetFont(&font);
726 BPoint where = ContentLocation();
727 where.x = fBounds.right - font.Size();
729 if (fSubmenu)
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));
735 else
736 fSuper->DrawChar(fShortcutChar, where + BPoint(0, ascent));
738 where.y += (fBounds.Height() - 11) / 2 - 1;
739 where.x -= 4;
741 // TODO: It would be nice to draw these taking into account the text (low)
742 // color.
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);
773 void
774 BMenuItem::_DrawSubmenuSymbol()
776 fSuper->PushState();
778 BRect r(fBounds);
779 float rightMargin;
780 MenuPrivate(fSuper).GetItemMargins(NULL, NULL, &rightMargin, NULL);
781 r.left = r.right - rightMargin + 3;
782 r.right -= 1;
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);
793 BShape arrowShape;
794 center.x += 0.5;
795 center.y += 0.5;
796 size *= 0.25;
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);
809 fSuper->PopState();
813 void
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);
826 void
827 BMenuItem::SetAutomaticTrigger(int32 index, uint32 trigger)
829 fTriggerIndex = index;
830 fTrigger = trigger;