tcp: Fix 64 bit build with debugging features enabled.
[haiku.git] / src / kits / interface / MenuItem.cpp
blobe1a20a8c9866663f171cf230a1d0bbab4173c906
1 /*
2 * Copyright 2001-2013 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 = (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[] = {
35 NULL,
36 "\xe2\x86\xb8", /* B_HOME U+21B8 */
37 NULL, NULL,
38 NULL, /* B_END */
39 NULL, /* B_INSERT */
40 NULL, NULL,
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 */
45 NULL, /* B_PAGE_UP */
46 NULL, /* B_PAGE_DOWN */
47 NULL, NULL, NULL,
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,
59 uint32 modifiers)
61 _InitData();
62 if (label != NULL)
63 fLabel = strdup(label);
65 SetMessage(message);
67 fShortcutChar = shortcut;
69 if (shortcut != 0)
70 fModifiers = modifiers | B_COMMAND_KEY;
71 else
72 fModifiers = 0;
76 BMenuItem::BMenuItem(BMenu* menu, BMessage* message)
78 _InitData();
79 SetMessage(message);
80 _InitMenuData(menu);
84 BMenuItem::BMenuItem(BMessage* data)
86 _InitData();
88 if (data->HasString("_label")) {
89 const char *string;
91 data->FindString("_label", &string);
92 SetLabel(string);
95 bool disable;
96 if (data->FindBool("_disable", &disable) == B_OK)
97 SetEnabled(!disable);
99 bool marked;
100 if (data->FindBool("_marked", &marked) == B_OK)
101 SetMarked(marked);
103 int32 userTrigger;
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);
119 SetMessage(msg);
122 BMessage subMessage;
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);
127 if (menu != NULL)
128 _InitMenuData(menu);
134 BArchivable*
135 BMenuItem::Instantiate(BMessage* data)
137 if (validate_instantiation(data, "BMenuItem"))
138 return new BMenuItem(data);
140 return NULL;
144 status_t
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);
163 if (ret == B_OK)
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) {
171 BMessage submenu;
172 if (fSubmenu->Archive(&submenu, true) == B_OK)
173 ret = data->AddMessage("_submenu", &submenu);
176 return ret;
180 BMenuItem::~BMenuItem()
182 free(fLabel);
183 delete fSubmenu;
187 void
188 BMenuItem::SetLabel(const char* string)
190 if (fLabel != NULL) {
191 free(fLabel);
192 fLabel = NULL;
195 if (string != NULL)
196 fLabel = strdup(string);
198 if (fSuper != NULL) {
199 fSuper->InvalidateLayout();
201 if (fSuper->LockLooper()) {
202 fSuper->Invalidate();
203 fSuper->UnlockLooper();
209 void
210 BMenuItem::SetEnabled(bool enable)
212 if (fEnabled == enable)
213 return;
215 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();
228 void
229 BMenuItem::SetMarked(bool mark)
231 fMark = mark;
233 if (mark && fSuper != NULL) {
234 MenuPrivate priv(fSuper);
235 priv.ItemMarked(this);
240 void
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);
250 if (pos == NULL) {
251 // take lowercase, too
252 pos = strchr(Label(), trigger);
255 if (pos != NULL) {
256 fTriggerIndex = UTF8CountChars(Label(), pos - Label());
257 fTrigger = trigger;
258 } else {
259 fTrigger = 0;
260 fTriggerIndex = -1;
263 if (fSuper != NULL)
264 fSuper->InvalidateLayout();
268 void
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;
278 if (shortcut != 0)
279 fModifiers = modifiers | B_COMMAND_KEY;
280 else
281 fModifiers = 0;
283 if (fShortcutChar != 0 && (fModifiers & B_COMMAND_KEY) && fWindow)
284 fWindow->AddShortcut(fShortcutChar, fModifiers, this);
286 if (fSuper) {
287 fSuper->InvalidateLayout();
289 if (fSuper->LockLooper()) {
290 fSuper->Invalidate();
291 fSuper->UnlockLooper();
297 const char*
298 BMenuItem::Label() const
300 return fLabel;
304 bool
305 BMenuItem::IsEnabled() const
307 if (fSubmenu)
308 return fSubmenu->IsEnabled();
310 if (!fEnabled)
311 return false;
313 return fSuper != NULL ? fSuper->IsEnabled() : true;
317 bool
318 BMenuItem::IsMarked() const
320 return fMark;
324 char
325 BMenuItem::Trigger() const
327 return fUserTrigger;
331 char
332 BMenuItem::Shortcut(uint32* modifiers) const
334 if (modifiers)
335 *modifiers = fModifiers;
337 return fShortcutChar;
341 BMenu*
342 BMenuItem::Submenu() const
344 return fSubmenu;
348 BMenu*
349 BMenuItem::Menu() const
351 return fSuper;
355 BRect
356 BMenuItem::Frame() const
358 return fBounds;
362 void
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);
372 if (_width)
373 *_width = (float)ceil(fCachedWidth);
374 if (_height)
375 *_height = MenuPrivate(fSuper).FontHeight();
379 void
380 BMenuItem::TruncateLabel(float maxWidth, char* newLabel)
382 BFont font;
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';
394 void
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);
405 float labelWidth;
406 float labelHeight;
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);
416 else {
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];
426 BFont font;
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();
434 lineStart.x--;
435 lineStart.y++;
437 BPoint lineEnd(lineStart);
438 lineEnd.x += escapements[fTriggerIndex] * font.Size();
440 fSuper->StrokeLine(lineStart, lineEnd);
445 void
446 BMenuItem::Draw()
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);
455 // set low color
456 if (activated) {
457 fSuper->SetLowColor(ui_color(B_MENU_SELECTED_BACKGROUND_COLOR));
458 // fill in the background
459 BRect rect(Frame());
460 be_control_look->DrawMenuItemBackground(fSuper, rect, Frame(),
461 fSuper->LowColor(), BControlLook::B_ACTIVATED);
462 } else
463 fSuper->SetLowColor(ui_color(B_MENU_BACKGROUND_COLOR));
465 // set high color
466 if (activated && enabled)
467 fSuper->SetHighColor(ui_color(B_MENU_SELECTED_ITEM_TEXT_COLOR));
468 else if (enabled)
469 fSuper->SetHighColor(ui_color(B_MENU_ITEM_TEXT_COLOR));
470 else {
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));
474 else
475 fSuper->SetHighColor(tint_color(bgColor, B_LIGHTEN_2_TINT));
478 // draw content
479 fSuper->MovePenTo(ContentLocation());
480 DrawContent();
482 // draw extra symbols
483 const menu_layout layout = MenuPrivate(fSuper).Layout();
484 if (layout == B_ITEMS_IN_COLUMN) {
485 if (IsMarked())
486 _DrawMarkSymbol();
488 if (fShortcutChar)
489 _DrawShortcutSymbol();
491 if (Submenu())
492 _DrawSubmenuSymbol();
495 // restore the parent menu's low color and high color
496 fSuper->SetLowColor(lowColor);
497 fSuper->SetHighColor(highColor);
501 void
502 BMenuItem::Highlight(bool highlight)
504 fSuper->Invalidate(Frame());
508 bool
509 BMenuItem::IsSelected() const
511 return fSelected;
515 BPoint
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 &)
535 BMenuItem&
536 BMenuItem::operator=(const BMenuItem &)
538 return *this;
542 void
543 BMenuItem::_InitData()
545 fLabel = NULL;
546 fSubmenu = NULL;
547 fWindow = NULL;
548 fSuper = NULL;
549 fModifiers = 0;
550 fCachedWidth = 0;
551 fTriggerIndex = -1;
552 fUserTrigger = 0;
553 fTrigger = 0;
554 fShortcutChar = 0;
555 fMark = false;
556 fEnabled = true;
557 fSelected = false;
561 void
562 BMenuItem::_InitMenuData(BMenu* menu)
564 fSubmenu = menu;
566 MenuPrivate(fSubmenu).SetSuperItem(this);
568 BMenuItem* item = menu->FindMarked();
570 if (menu->IsRadioMode() && menu->IsLabelFromMarked() && item != NULL)
571 SetLabel(item->Label());
572 else
573 SetLabel(menu->Name());
577 void
578 BMenuItem::Install(BWindow* window)
580 if (fSubmenu != NULL)
581 MenuPrivate(fSubmenu).Install(window);
583 fWindow = window;
585 if (fShortcutChar != 0 && (fModifiers & B_COMMAND_KEY) && fWindow)
586 window->AddShortcut(fShortcutChar, fModifiers, this);
588 if (!Messenger().IsValid())
589 SetTarget(window);
593 status_t
594 BMenuItem::Invoke(BMessage* message)
596 if (!IsEnabled())
597 return B_ERROR;
599 if (fSuper->IsRadioMode())
600 SetMarked(true);
602 bool notify = false;
603 uint32 kind = InvokeKind(&notify);
605 BMessage clone(kind);
606 status_t err = B_BAD_VALUE;
608 if (message == NULL && !notify)
609 message = Message();
611 if (message == NULL) {
612 if (!fSuper->IsWatched())
613 return err;
614 } else
615 clone = *message;
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));
622 if (message != NULL)
623 err = BInvoker::Invoke(&clone);
625 // TODO: assynchronous messaging
626 // SendNotices(kind, &clone);
628 return err;
632 void
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);
646 fWindow = NULL;
650 void
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);
661 fSuper = super;
665 void
666 BMenuItem::Select(bool selected)
668 if (fSelected == selected)
669 return;
671 if (Submenu() != NULL || IsEnabled()) {
672 fSelected = selected;
673 Highlight(selected);
678 void
679 BMenuItem::_DrawMarkSymbol()
681 fSuper->PushState();
683 BRect r(fBounds);
684 float leftMargin;
685 MenuPrivate(fSuper).GetItemMargins(&leftMargin, NULL, NULL, NULL);
686 r.right = r.left + leftMargin - 3;
687 r.left += 1;
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);
698 BShape arrowShape;
699 center.x += 0.5;
700 center.y += 0.5;
701 size *= 0.3;
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);
713 fSuper->PopState();
717 void
718 BMenuItem::_DrawShortcutSymbol()
720 BMenu* menu = fSuper;
721 BFont font;
722 menu->GetFont(&font);
723 BPoint where = ContentLocation();
724 where.x = fBounds.right - font.Size();
726 if (fSubmenu)
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));
732 else
733 fSuper->DrawChar(fShortcutChar, where + BPoint(0, ascent));
735 where.y += (fBounds.Height() - 11) / 2 - 1;
736 where.x -= 4;
738 // TODO: It would be nice to draw these taking into account the text (low)
739 // color.
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);
770 void
771 BMenuItem::_DrawSubmenuSymbol()
773 fSuper->PushState();
775 BRect r(fBounds);
776 float rightMargin;
777 MenuPrivate(fSuper).GetItemMargins(NULL, NULL, &rightMargin, NULL);
778 r.left = r.right - rightMargin + 3;
779 r.right -= 1;
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);
790 BShape arrowShape;
791 center.x += 0.5;
792 center.y += 0.5;
793 size *= 0.25;
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);
806 fSuper->PopState();
810 void
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);
823 void
824 BMenuItem::SetAutomaticTrigger(int32 index, uint32 trigger)
826 fTriggerIndex = index;
827 fTrigger = trigger;