btrfs: Attempt to fix GCC2 build.
[haiku.git] / src / kits / interface / Menu.cpp
blob98b1054927a03c3a70bf311f3a606dc6362e1f08
1 /*
2 * Copyright 2001-2015 Haiku, Inc. All rights reserved.
3 * Distributed under the terms of the MIT license.
5 * Authors:
6 * Stephan Aßmus, superstippi@gmx.de
7 * Stefano Ceccherini, stefano.ceccherini@gmail.com
8 * Marc Flerackers, mflerackers@androme.be
9 * Rene Gollent, anevilyak@gmail.com
10 * John Scipione, jscipione@gmail.com
14 #include <Menu.h>
16 #include <algorithm>
17 #include <new>
19 #include <ctype.h>
20 #include <string.h>
22 #include <Application.h>
23 #include <Bitmap.h>
24 #include <ControlLook.h>
25 #include <Debug.h>
26 #include <File.h>
27 #include <FindDirectory.h>
28 #include <Layout.h>
29 #include <LayoutUtils.h>
30 #include <MenuBar.h>
31 #include <MenuItem.h>
32 #include <Messenger.h>
33 #include <Path.h>
34 #include <PropertyInfo.h>
35 #include <Screen.h>
36 #include <ScrollBar.h>
37 #include <SystemCatalog.h>
38 #include <Window.h>
40 #include <AppServerLink.h>
41 #include <binary_compatibility/Interface.h>
42 #include <BMCPrivate.h>
43 #include <MenuPrivate.h>
44 #include <MenuWindow.h>
45 #include <ServerProtocol.h>
47 #include "utf8_functions.h"
50 #define USE_CACHED_MENUWINDOW 1
52 using BPrivate::gSystemCatalog;
54 #undef B_TRANSLATION_CONTEXT
55 #define B_TRANSLATION_CONTEXT "Menu"
57 #undef B_TRANSLATE
58 #define B_TRANSLATE(str) \
59 gSystemCatalog.GetString(B_TRANSLATE_MARK(str), "Menu")
62 using std::nothrow;
63 using BPrivate::BMenuWindow;
65 namespace BPrivate {
67 class TriggerList {
68 public:
69 TriggerList() {}
70 ~TriggerList() {}
72 // TODO: make this work with Unicode characters!
74 bool HasTrigger(uint32 c)
75 { return fList.HasItem((void*)(addr_t)tolower(c)); }
76 bool AddTrigger(uint32 c)
77 { return fList.AddItem((void*)(addr_t)tolower(c)); }
79 private:
80 BList fList;
84 class ExtraMenuData {
85 public:
86 menu_tracking_hook trackingHook;
87 void* trackingState;
89 ExtraMenuData(menu_tracking_hook func, void* state)
91 trackingHook = func;
92 trackingState = state;
97 } // namespace BPrivate
100 menu_info BMenu::sMenuInfo;
102 uint32 BMenu::sShiftKey;
103 uint32 BMenu::sControlKey;
104 uint32 BMenu::sOptionKey;
105 uint32 BMenu::sCommandKey;
106 uint32 BMenu::sMenuKey;
108 static property_info sPropList[] = {
109 { "Enabled", { B_GET_PROPERTY, 0 },
110 { B_DIRECT_SPECIFIER, 0 }, "Returns true if menu or menu item is "
111 "enabled; false otherwise.",
112 0, { B_BOOL_TYPE }
115 { "Enabled", { B_SET_PROPERTY, 0 },
116 { B_DIRECT_SPECIFIER, 0 }, "Enables or disables menu or menu item.",
117 0, { B_BOOL_TYPE }
120 { "Label", { B_GET_PROPERTY, 0 },
121 { B_DIRECT_SPECIFIER, 0 }, "Returns the string label of the menu or "
122 "menu item.",
123 0, { B_STRING_TYPE }
126 { "Label", { B_SET_PROPERTY, 0 },
127 { B_DIRECT_SPECIFIER, 0 }, "Sets the string label of the menu or menu "
128 "item.",
129 0, { B_STRING_TYPE }
132 { "Mark", { B_GET_PROPERTY, 0 },
133 { B_DIRECT_SPECIFIER, 0 }, "Returns true if the menu item or the "
134 "menu's superitem is marked; false otherwise.",
135 0, { B_BOOL_TYPE }
138 { "Mark", { B_SET_PROPERTY, 0 },
139 { B_DIRECT_SPECIFIER, 0 }, "Marks or unmarks the menu item or the "
140 "menu's superitem.",
141 0, { B_BOOL_TYPE }
144 { "Menu", { B_CREATE_PROPERTY, 0 },
145 { B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 },
146 "Adds a new menu item at the specified index with the text label "
147 "found in \"data\" and the int32 command found in \"what\" (used as "
148 "the what field in the BMessage sent by the item)." , 0, {},
149 { {{{"data", B_STRING_TYPE}}}
153 { "Menu", { B_DELETE_PROPERTY, 0 },
154 { B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 },
155 "Removes the selected menu or menus.", 0, {}
158 { "Menu", { },
159 { B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 },
160 "Directs scripting message to the specified menu, first popping the "
161 "current specifier off the stack.", 0, {}
164 { "MenuItem", { B_COUNT_PROPERTIES, 0 },
165 { B_DIRECT_SPECIFIER, 0 }, "Counts the number of menu items in the "
166 "specified menu.",
167 0, { B_INT32_TYPE }
170 { "MenuItem", { B_CREATE_PROPERTY, 0 },
171 { B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 },
172 "Adds a new menu item at the specified index with the text label "
173 "found in \"data\" and the int32 command found in \"what\" (used as "
174 "the what field in the BMessage sent by the item).", 0, {},
175 { { {{"data", B_STRING_TYPE },
176 {"be:invoke_message", B_MESSAGE_TYPE},
177 {"what", B_INT32_TYPE},
178 {"be:target", B_MESSENGER_TYPE}} }
182 { "MenuItem", { B_DELETE_PROPERTY, 0 },
183 { B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 },
184 "Removes the specified menu item from its parent menu."
187 { "MenuItem", { B_EXECUTE_PROPERTY, 0 },
188 { B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 },
189 "Invokes the specified menu item."
192 { "MenuItem", { },
193 { B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 },
194 "Directs scripting message to the specified menu, first popping the "
195 "current specifier off the stack."
198 { 0 }
202 // note: this is redefined to localized one in BMenu::_InitData
203 const char* BPrivate::kEmptyMenuLabel = "<empty>";
206 struct BMenu::LayoutData {
207 BSize preferred;
208 uint32 lastResizingMode;
212 // #pragma mark - BMenu
215 BMenu::BMenu(const char* name, menu_layout layout)
217 BView(BRect(0, 0, 0, 0), name, 0, B_WILL_DRAW),
218 fChosenItem(NULL),
219 fPad(std::max(14.0f, be_plain_font->Size() + 2.0f), 2.0f, 20.0f, 0.0f),
220 fSelected(NULL),
221 fCachedMenuWindow(NULL),
222 fSuper(NULL),
223 fSuperitem(NULL),
224 fAscent(-1.0f),
225 fDescent(-1.0f),
226 fFontHeight(-1.0f),
227 fState(MENU_STATE_CLOSED),
228 fLayout(layout),
229 fExtraRect(NULL),
230 fMaxContentWidth(0.0f),
231 fInitMatrixSize(NULL),
232 fExtraMenuData(NULL),
233 fTrigger(0),
234 fResizeToFit(true),
235 fUseCachedMenuLayout(false),
236 fEnabled(true),
237 fDynamicName(false),
238 fRadioMode(false),
239 fTrackNewBounds(false),
240 fStickyMode(false),
241 fIgnoreHidden(true),
242 fTriggerEnabled(true),
243 fRedrawAfterSticky(false),
244 fAttachAborted(false)
246 _InitData(NULL);
250 BMenu::BMenu(const char* name, float width, float height)
252 BView(BRect(0.0f, 0.0f, 0.0f, 0.0f), name, 0, B_WILL_DRAW),
253 fChosenItem(NULL),
254 fPad(14.0f, 2.0f, 20.0f, 0.0f),
255 fSelected(NULL),
256 fCachedMenuWindow(NULL),
257 fSuper(NULL),
258 fSuperitem(NULL),
259 fAscent(-1.0f),
260 fDescent(-1.0f),
261 fFontHeight(-1.0f),
262 fState(0),
263 fLayout(B_ITEMS_IN_MATRIX),
264 fExtraRect(NULL),
265 fMaxContentWidth(0.0f),
266 fInitMatrixSize(NULL),
267 fExtraMenuData(NULL),
268 fTrigger(0),
269 fResizeToFit(true),
270 fUseCachedMenuLayout(false),
271 fEnabled(true),
272 fDynamicName(false),
273 fRadioMode(false),
274 fTrackNewBounds(false),
275 fStickyMode(false),
276 fIgnoreHidden(true),
277 fTriggerEnabled(true),
278 fRedrawAfterSticky(false),
279 fAttachAborted(false)
281 _InitData(NULL);
285 BMenu::BMenu(BMessage* archive)
287 BView(archive),
288 fChosenItem(NULL),
289 fPad(14.0f, 2.0f, 20.0f, 0.0f),
290 fSelected(NULL),
291 fCachedMenuWindow(NULL),
292 fSuper(NULL),
293 fSuperitem(NULL),
294 fAscent(-1.0f),
295 fDescent(-1.0f),
296 fFontHeight(-1.0f),
297 fState(MENU_STATE_CLOSED),
298 fLayout(B_ITEMS_IN_ROW),
299 fExtraRect(NULL),
300 fMaxContentWidth(0.0f),
301 fInitMatrixSize(NULL),
302 fExtraMenuData(NULL),
303 fTrigger(0),
304 fResizeToFit(true),
305 fUseCachedMenuLayout(false),
306 fEnabled(true),
307 fDynamicName(false),
308 fRadioMode(false),
309 fTrackNewBounds(false),
310 fStickyMode(false),
311 fIgnoreHidden(true),
312 fTriggerEnabled(true),
313 fRedrawAfterSticky(false),
314 fAttachAborted(false)
316 _InitData(archive);
320 BMenu::~BMenu()
322 _DeleteMenuWindow();
324 RemoveItems(0, CountItems(), true);
326 delete fInitMatrixSize;
327 delete fExtraMenuData;
328 delete fLayoutData;
332 BArchivable*
333 BMenu::Instantiate(BMessage* archive)
335 if (validate_instantiation(archive, "BMenu"))
336 return new (nothrow) BMenu(archive);
338 return NULL;
342 status_t
343 BMenu::Archive(BMessage* data, bool deep) const
345 status_t err = BView::Archive(data, deep);
347 if (err == B_OK && Layout() != B_ITEMS_IN_ROW)
348 err = data->AddInt32("_layout", Layout());
349 if (err == B_OK)
350 err = data->AddBool("_rsize_to_fit", fResizeToFit);
351 if (err == B_OK)
352 err = data->AddBool("_disable", !IsEnabled());
353 if (err == B_OK)
354 err = data->AddBool("_radio", IsRadioMode());
355 if (err == B_OK)
356 err = data->AddBool("_trig_disabled", AreTriggersEnabled());
357 if (err == B_OK)
358 err = data->AddBool("_dyn_label", fDynamicName);
359 if (err == B_OK)
360 err = data->AddFloat("_maxwidth", fMaxContentWidth);
361 if (err == B_OK && deep) {
362 BMenuItem* item = NULL;
363 int32 index = 0;
364 while ((item = ItemAt(index++)) != NULL) {
365 BMessage itemData;
366 item->Archive(&itemData, deep);
367 err = data->AddMessage("_items", &itemData);
368 if (err != B_OK)
369 break;
370 if (fLayout == B_ITEMS_IN_MATRIX) {
371 err = data->AddRect("_i_frames", item->fBounds);
376 return err;
380 void
381 BMenu::AttachedToWindow()
383 BView::AttachedToWindow();
385 _GetShiftKey(sShiftKey);
386 _GetControlKey(sControlKey);
387 _GetCommandKey(sCommandKey);
388 _GetOptionKey(sOptionKey);
389 _GetMenuKey(sMenuKey);
391 fAttachAborted = _AddDynamicItems();
393 if (!fAttachAborted) {
394 _CacheFontInfo();
395 _LayoutItems(0);
396 _UpdateWindowViewSize(false);
401 void
402 BMenu::DetachedFromWindow()
404 BView::DetachedFromWindow();
408 void
409 BMenu::AllAttached()
411 BView::AllAttached();
415 void
416 BMenu::AllDetached()
418 BView::AllDetached();
422 void
423 BMenu::Draw(BRect updateRect)
425 if (_RelayoutIfNeeded()) {
426 Invalidate();
427 return;
430 DrawBackground(updateRect);
431 DrawItems(updateRect);
435 void
436 BMenu::MessageReceived(BMessage* message)
438 switch (message->what) {
439 case B_MOUSE_WHEEL_CHANGED:
441 float deltaY = 0;
442 message->FindFloat("be:wheel_delta_y", &deltaY);
443 if (deltaY == 0)
444 return;
446 BMenuWindow* window = dynamic_cast<BMenuWindow*>(Window());
447 if (window == NULL)
448 return;
450 float largeStep;
451 float smallStep;
452 window->GetSteps(&smallStep, &largeStep);
454 // pressing the shift key scrolls faster
455 if ((modifiers() & B_SHIFT_KEY) != 0)
456 deltaY *= largeStep;
457 else
458 deltaY *= smallStep;
460 window->TryScrollBy(deltaY);
461 break;
464 default:
465 BView::MessageReceived(message);
466 break;
471 void
472 BMenu::KeyDown(const char* bytes, int32 numBytes)
474 // TODO: Test how it works on BeOS R5 and implement this correctly
475 switch (bytes[0]) {
476 case B_UP_ARROW:
477 if (fLayout == B_ITEMS_IN_COLUMN)
478 _SelectNextItem(fSelected, false);
479 break;
481 case B_DOWN_ARROW:
483 BMenuBar* bar = dynamic_cast<BMenuBar*>(Supermenu());
484 if (bar != NULL && fState == MENU_STATE_CLOSED) {
485 // tell MenuBar's _Track:
486 bar->fState = MENU_STATE_KEY_TO_SUBMENU;
488 if (fLayout == B_ITEMS_IN_COLUMN)
489 _SelectNextItem(fSelected, true);
490 break;
493 case B_LEFT_ARROW:
494 if (fLayout == B_ITEMS_IN_ROW)
495 _SelectNextItem(fSelected, false);
496 else {
497 // this case has to be handled a bit specially.
498 BMenuItem* item = Superitem();
499 if (item) {
500 if (dynamic_cast<BMenuBar*>(Supermenu())) {
501 // If we're at the top menu below the menu bar, pass
502 // the keypress to the menu bar so we can move to
503 // another top level menu.
504 BMessenger messenger(Supermenu());
505 messenger.SendMessage(Window()->CurrentMessage());
506 } else {
507 // tell _Track
508 fState = MENU_STATE_KEY_LEAVE_SUBMENU;
512 break;
514 case B_RIGHT_ARROW:
515 if (fLayout == B_ITEMS_IN_ROW)
516 _SelectNextItem(fSelected, true);
517 else {
518 if (fSelected != NULL && fSelected->Submenu() != NULL) {
519 fSelected->Submenu()->_SetStickyMode(true);
520 // fix me: this shouldn't be needed but dynamic menus
521 // aren't getting it set correctly when keyboard
522 // navigating, which aborts the attach
523 fState = MENU_STATE_KEY_TO_SUBMENU;
524 _SelectItem(fSelected, true, true, true);
525 } else if (dynamic_cast<BMenuBar*>(Supermenu())) {
526 // if we have no submenu and we're an
527 // item in the top menu below the menubar,
528 // pass the keypress to the menubar
529 // so you can use the keypress to switch menus.
530 BMessenger messenger(Supermenu());
531 messenger.SendMessage(Window()->CurrentMessage());
534 break;
536 case B_PAGE_UP:
537 case B_PAGE_DOWN:
539 BMenuWindow* window = dynamic_cast<BMenuWindow*>(Window());
540 if (window == NULL || !window->HasScrollers())
541 break;
543 int32 deltaY = bytes[0] == B_PAGE_UP ? -1 : 1;
545 float largeStep;
546 window->GetSteps(NULL, &largeStep);
547 window->TryScrollBy(deltaY * largeStep);
548 break;
551 case B_ENTER:
552 case B_SPACE:
553 if (fSelected != NULL) {
554 fChosenItem = fSelected;
555 // preserve for exit handling
556 _QuitTracking(false);
558 break;
560 case B_ESCAPE:
561 _SelectItem(NULL);
562 if (fState == MENU_STATE_CLOSED
563 && dynamic_cast<BMenuBar*>(Supermenu())) {
564 // Keyboard may show menu without tracking it
565 BMessenger messenger(Supermenu());
566 messenger.SendMessage(Window()->CurrentMessage());
567 } else
568 _QuitTracking(false);
569 break;
571 default:
573 uint32 trigger = UTF8ToCharCode(&bytes);
575 for (uint32 i = CountItems(); i-- > 0;) {
576 BMenuItem* item = ItemAt(i);
577 if (item->fTriggerIndex < 0 || item->fTrigger != trigger)
578 continue;
580 _InvokeItem(item);
581 _QuitTracking(false);
582 break;
584 break;
590 BSize
591 BMenu::MinSize()
593 _ValidatePreferredSize();
595 BSize size = (GetLayout() != NULL ? GetLayout()->MinSize()
596 : fLayoutData->preferred);
598 return BLayoutUtils::ComposeSize(ExplicitMinSize(), size);
602 BSize
603 BMenu::MaxSize()
605 _ValidatePreferredSize();
607 BSize size = (GetLayout() != NULL ? GetLayout()->MaxSize()
608 : fLayoutData->preferred);
610 return BLayoutUtils::ComposeSize(ExplicitMaxSize(), size);
614 BSize
615 BMenu::PreferredSize()
617 _ValidatePreferredSize();
619 BSize size = (GetLayout() != NULL ? GetLayout()->PreferredSize()
620 : fLayoutData->preferred);
622 return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), size);
626 void
627 BMenu::GetPreferredSize(float* _width, float* _height)
629 _ValidatePreferredSize();
631 if (_width)
632 *_width = fLayoutData->preferred.width;
634 if (_height)
635 *_height = fLayoutData->preferred.height;
639 void
640 BMenu::ResizeToPreferred()
642 BView::ResizeToPreferred();
646 void
647 BMenu::DoLayout()
649 // If the user set a layout, we let the base class version call its
650 // hook.
651 if (GetLayout() != NULL) {
652 BView::DoLayout();
653 return;
656 if (_RelayoutIfNeeded())
657 Invalidate();
661 void
662 BMenu::FrameMoved(BPoint new_position)
664 BView::FrameMoved(new_position);
668 void
669 BMenu::FrameResized(float new_width, float new_height)
671 BView::FrameResized(new_width, new_height);
675 void
676 BMenu::InvalidateLayout()
678 fUseCachedMenuLayout = false;
679 // This method exits for backwards compatibility reasons, it is used to
680 // invalidate the menu layout, but we also use call
681 // BView::InvalidateLayout() for good measure. Don't delete this method!
682 BView::InvalidateLayout(false);
686 void
687 BMenu::MakeFocus(bool focused)
689 BView::MakeFocus(focused);
693 bool
694 BMenu::AddItem(BMenuItem* item)
696 return AddItem(item, CountItems());
700 bool
701 BMenu::AddItem(BMenuItem* item, int32 index)
703 if (fLayout == B_ITEMS_IN_MATRIX) {
704 debugger("BMenu::AddItem(BMenuItem*, int32) this method can only "
705 "be called if the menu layout is not B_ITEMS_IN_MATRIX");
708 if (item == NULL || !_AddItem(item, index))
709 return false;
711 InvalidateLayout();
712 if (LockLooper()) {
713 if (!Window()->IsHidden()) {
714 _LayoutItems(index);
715 _UpdateWindowViewSize(false);
716 Invalidate();
718 UnlockLooper();
721 return true;
725 bool
726 BMenu::AddItem(BMenuItem* item, BRect frame)
728 if (fLayout != B_ITEMS_IN_MATRIX) {
729 debugger("BMenu::AddItem(BMenuItem*, BRect) this method can only "
730 "be called if the menu layout is B_ITEMS_IN_MATRIX");
733 if (item == NULL)
734 return false;
736 item->fBounds = frame;
738 int32 index = CountItems();
739 if (!_AddItem(item, index))
740 return false;
742 if (LockLooper()) {
743 if (!Window()->IsHidden()) {
744 _LayoutItems(index);
745 Invalidate();
747 UnlockLooper();
750 return true;
754 bool
755 BMenu::AddItem(BMenu* submenu)
757 BMenuItem* item = new (nothrow) BMenuItem(submenu);
758 if (item == NULL)
759 return false;
761 if (!AddItem(item, CountItems())) {
762 item->fSubmenu = NULL;
763 delete item;
764 return false;
767 return true;
771 bool
772 BMenu::AddItem(BMenu* submenu, int32 index)
774 if (fLayout == B_ITEMS_IN_MATRIX) {
775 debugger("BMenu::AddItem(BMenuItem*, int32) this method can only "
776 "be called if the menu layout is not B_ITEMS_IN_MATRIX");
779 BMenuItem* item = new (nothrow) BMenuItem(submenu);
780 if (item == NULL)
781 return false;
783 if (!AddItem(item, index)) {
784 item->fSubmenu = NULL;
785 delete item;
786 return false;
789 return true;
793 bool
794 BMenu::AddItem(BMenu* submenu, BRect frame)
796 if (fLayout != B_ITEMS_IN_MATRIX) {
797 debugger("BMenu::AddItem(BMenu*, BRect) this method can only "
798 "be called if the menu layout is B_ITEMS_IN_MATRIX");
801 BMenuItem* item = new (nothrow) BMenuItem(submenu);
802 if (item == NULL)
803 return false;
805 if (!AddItem(item, frame)) {
806 item->fSubmenu = NULL;
807 delete item;
808 return false;
811 return true;
815 bool
816 BMenu::AddList(BList* list, int32 index)
818 // TODO: test this function, it's not documented in the bebook.
819 if (list == NULL)
820 return false;
822 bool locked = LockLooper();
824 int32 numItems = list->CountItems();
825 for (int32 i = 0; i < numItems; i++) {
826 BMenuItem* item = static_cast<BMenuItem*>(list->ItemAt(i));
827 if (item != NULL) {
828 if (!_AddItem(item, index + i))
829 break;
833 InvalidateLayout();
834 if (locked && Window() != NULL && !Window()->IsHidden()) {
835 // Make sure we update the layout if needed.
836 _LayoutItems(index);
837 _UpdateWindowViewSize(false);
838 Invalidate();
841 if (locked)
842 UnlockLooper();
844 return true;
848 bool
849 BMenu::AddSeparatorItem()
851 BMenuItem* item = new (nothrow) BSeparatorItem();
852 if (!item || !AddItem(item, CountItems())) {
853 delete item;
854 return false;
857 return true;
861 bool
862 BMenu::RemoveItem(BMenuItem* item)
864 return _RemoveItems(0, 0, item, false);
868 BMenuItem*
869 BMenu::RemoveItem(int32 index)
871 BMenuItem* item = ItemAt(index);
872 if (item != NULL)
873 _RemoveItems(0, 0, item, false);
874 return item;
878 bool
879 BMenu::RemoveItems(int32 index, int32 count, bool deleteItems)
881 return _RemoveItems(index, count, NULL, deleteItems);
885 bool
886 BMenu::RemoveItem(BMenu* submenu)
888 for (int32 i = 0; i < fItems.CountItems(); i++) {
889 if (static_cast<BMenuItem*>(fItems.ItemAtFast(i))->Submenu()
890 == submenu) {
891 return _RemoveItems(i, 1, NULL, false);
895 return false;
899 int32
900 BMenu::CountItems() const
902 return fItems.CountItems();
906 BMenuItem*
907 BMenu::ItemAt(int32 index) const
909 return static_cast<BMenuItem*>(fItems.ItemAt(index));
913 BMenu*
914 BMenu::SubmenuAt(int32 index) const
916 BMenuItem* item = static_cast<BMenuItem*>(fItems.ItemAt(index));
917 return item != NULL ? item->Submenu() : NULL;
921 int32
922 BMenu::IndexOf(BMenuItem* item) const
924 return fItems.IndexOf(item);
928 int32
929 BMenu::IndexOf(BMenu* submenu) const
931 for (int32 i = 0; i < fItems.CountItems(); i++) {
932 if (ItemAt(i)->Submenu() == submenu)
933 return i;
936 return -1;
940 BMenuItem*
941 BMenu::FindItem(const char* label) const
943 BMenuItem* item = NULL;
945 for (int32 i = 0; i < CountItems(); i++) {
946 item = ItemAt(i);
948 if (item->Label() && strcmp(item->Label(), label) == 0)
949 return item;
951 if (item->Submenu() != NULL) {
952 item = item->Submenu()->FindItem(label);
953 if (item != NULL)
954 return item;
958 return NULL;
962 BMenuItem*
963 BMenu::FindItem(uint32 command) const
965 BMenuItem* item = NULL;
967 for (int32 i = 0; i < CountItems(); i++) {
968 item = ItemAt(i);
970 if (item->Command() == command)
971 return item;
973 if (item->Submenu() != NULL) {
974 item = item->Submenu()->FindItem(command);
975 if (item != NULL)
976 return item;
980 return NULL;
984 status_t
985 BMenu::SetTargetForItems(BHandler* handler)
987 status_t status = B_OK;
988 for (int32 i = 0; i < fItems.CountItems(); i++) {
989 status = ItemAt(i)->SetTarget(handler);
990 if (status < B_OK)
991 break;
994 return status;
998 status_t
999 BMenu::SetTargetForItems(BMessenger messenger)
1001 status_t status = B_OK;
1002 for (int32 i = 0; i < fItems.CountItems(); i++) {
1003 status = ItemAt(i)->SetTarget(messenger);
1004 if (status < B_OK)
1005 break;
1008 return status;
1012 void
1013 BMenu::SetEnabled(bool enable)
1015 if (fEnabled == enable)
1016 return;
1018 fEnabled = enable;
1020 if (dynamic_cast<_BMCMenuBar_*>(Supermenu()) != NULL)
1021 Supermenu()->SetEnabled(enable);
1023 if (fSuperitem)
1024 fSuperitem->SetEnabled(enable);
1028 void
1029 BMenu::SetRadioMode(bool on)
1031 fRadioMode = on;
1032 if (!on)
1033 SetLabelFromMarked(false);
1037 void
1038 BMenu::SetTriggersEnabled(bool enable)
1040 fTriggerEnabled = enable;
1044 void
1045 BMenu::SetMaxContentWidth(float width)
1047 fMaxContentWidth = width;
1051 void
1052 BMenu::SetLabelFromMarked(bool on)
1054 fDynamicName = on;
1055 if (on)
1056 SetRadioMode(true);
1060 bool
1061 BMenu::IsLabelFromMarked()
1063 return fDynamicName;
1067 bool
1068 BMenu::IsEnabled() const
1070 if (!fEnabled)
1071 return false;
1073 return fSuper ? fSuper->IsEnabled() : true ;
1077 bool
1078 BMenu::IsRadioMode() const
1080 return fRadioMode;
1084 bool
1085 BMenu::AreTriggersEnabled() const
1087 return fTriggerEnabled;
1091 bool
1092 BMenu::IsRedrawAfterSticky() const
1094 return fRedrawAfterSticky;
1098 float
1099 BMenu::MaxContentWidth() const
1101 return fMaxContentWidth;
1105 BMenuItem*
1106 BMenu::FindMarked()
1108 for (int32 i = 0; i < fItems.CountItems(); i++) {
1109 BMenuItem* item = ItemAt(i);
1111 if (item->IsMarked())
1112 return item;
1115 return NULL;
1119 int32
1120 BMenu::FindMarkedIndex()
1122 for (int32 i = 0; i < fItems.CountItems(); i++) {
1123 BMenuItem* item = ItemAt(i);
1125 if (item->IsMarked())
1126 return i;
1129 return -1;
1133 BMenu*
1134 BMenu::Supermenu() const
1136 return fSuper;
1140 BMenuItem*
1141 BMenu::Superitem() const
1143 return fSuperitem;
1147 BHandler*
1148 BMenu::ResolveSpecifier(BMessage* msg, int32 index, BMessage* specifier,
1149 int32 form, const char* property)
1151 BPropertyInfo propInfo(sPropList);
1152 BHandler* target = NULL;
1154 switch (propInfo.FindMatch(msg, 0, specifier, form, property)) {
1155 case B_ERROR:
1156 break;
1158 case 0:
1159 case 1:
1160 case 2:
1161 case 3:
1162 case 4:
1163 case 5:
1164 case 6:
1165 case 7:
1166 target = this;
1167 break;
1168 case 8:
1169 // TODO: redirect to menu
1170 target = this;
1171 break;
1172 case 9:
1173 case 10:
1174 case 11:
1175 case 12:
1176 target = this;
1177 break;
1178 case 13:
1179 // TODO: redirect to menuitem
1180 target = this;
1181 break;
1184 if (!target)
1185 target = BView::ResolveSpecifier(msg, index, specifier, form,
1186 property);
1188 return target;
1192 status_t
1193 BMenu::GetSupportedSuites(BMessage* data)
1195 if (data == NULL)
1196 return B_BAD_VALUE;
1198 status_t err = data->AddString("suites", "suite/vnd.Be-menu");
1200 if (err < B_OK)
1201 return err;
1203 BPropertyInfo propertyInfo(sPropList);
1204 err = data->AddFlat("messages", &propertyInfo);
1206 if (err < B_OK)
1207 return err;
1209 return BView::GetSupportedSuites(data);
1213 status_t
1214 BMenu::Perform(perform_code code, void* _data)
1216 switch (code) {
1217 case PERFORM_CODE_MIN_SIZE:
1218 ((perform_data_min_size*)_data)->return_value
1219 = BMenu::MinSize();
1220 return B_OK;
1222 case PERFORM_CODE_MAX_SIZE:
1223 ((perform_data_max_size*)_data)->return_value
1224 = BMenu::MaxSize();
1225 return B_OK;
1227 case PERFORM_CODE_PREFERRED_SIZE:
1228 ((perform_data_preferred_size*)_data)->return_value
1229 = BMenu::PreferredSize();
1230 return B_OK;
1232 case PERFORM_CODE_LAYOUT_ALIGNMENT:
1233 ((perform_data_layout_alignment*)_data)->return_value
1234 = BMenu::LayoutAlignment();
1235 return B_OK;
1237 case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH:
1238 ((perform_data_has_height_for_width*)_data)->return_value
1239 = BMenu::HasHeightForWidth();
1240 return B_OK;
1242 case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH:
1244 perform_data_get_height_for_width* data
1245 = (perform_data_get_height_for_width*)_data;
1246 BMenu::GetHeightForWidth(data->width, &data->min, &data->max,
1247 &data->preferred);
1248 return B_OK;
1251 case PERFORM_CODE_SET_LAYOUT:
1253 perform_data_set_layout* data = (perform_data_set_layout*)_data;
1254 BMenu::SetLayout(data->layout);
1255 return B_OK;
1258 case PERFORM_CODE_LAYOUT_INVALIDATED:
1260 perform_data_layout_invalidated* data
1261 = (perform_data_layout_invalidated*)_data;
1262 BMenu::LayoutInvalidated(data->descendants);
1263 return B_OK;
1266 case PERFORM_CODE_DO_LAYOUT:
1268 BMenu::DoLayout();
1269 return B_OK;
1273 return BView::Perform(code, _data);
1277 // #pragma mark - BMenu protected methods
1280 BMenu::BMenu(BRect frame, const char* name, uint32 resizingMode, uint32 flags,
1281 menu_layout layout, bool resizeToFit)
1283 BView(frame, name, resizingMode, flags),
1284 fChosenItem(NULL),
1285 fSelected(NULL),
1286 fCachedMenuWindow(NULL),
1287 fSuper(NULL),
1288 fSuperitem(NULL),
1289 fAscent(-1.0f),
1290 fDescent(-1.0f),
1291 fFontHeight(-1.0f),
1292 fState(MENU_STATE_CLOSED),
1293 fLayout(layout),
1294 fExtraRect(NULL),
1295 fMaxContentWidth(0.0f),
1296 fInitMatrixSize(NULL),
1297 fExtraMenuData(NULL),
1298 fTrigger(0),
1299 fResizeToFit(resizeToFit),
1300 fUseCachedMenuLayout(false),
1301 fEnabled(true),
1302 fDynamicName(false),
1303 fRadioMode(false),
1304 fTrackNewBounds(false),
1305 fStickyMode(false),
1306 fIgnoreHidden(true),
1307 fTriggerEnabled(true),
1308 fRedrawAfterSticky(false),
1309 fAttachAborted(false)
1311 _InitData(NULL);
1315 void
1316 BMenu::SetItemMargins(float left, float top, float right, float bottom)
1318 fPad.Set(left, top, right, bottom);
1322 void
1323 BMenu::GetItemMargins(float* _left, float* _top, float* _right,
1324 float* _bottom) const
1326 if (_left != NULL)
1327 *_left = fPad.left;
1329 if (_top != NULL)
1330 *_top = fPad.top;
1332 if (_right != NULL)
1333 *_right = fPad.right;
1335 if (_bottom != NULL)
1336 *_bottom = fPad.bottom;
1340 menu_layout
1341 BMenu::Layout() const
1343 return fLayout;
1347 void
1348 BMenu::Show()
1350 Show(false);
1354 void
1355 BMenu::Show(bool selectFirst)
1357 _Install(NULL);
1358 _Show(selectFirst);
1362 void
1363 BMenu::Hide()
1365 _Hide();
1366 _Uninstall();
1370 BMenuItem*
1371 BMenu::Track(bool sticky, BRect* clickToOpenRect)
1373 if (sticky && LockLooper()) {
1374 //RedrawAfterSticky(Bounds());
1375 // the call above didn't do anything, so I've removed it for now
1376 UnlockLooper();
1379 if (clickToOpenRect != NULL && LockLooper()) {
1380 fExtraRect = clickToOpenRect;
1381 ConvertFromScreen(fExtraRect);
1382 UnlockLooper();
1385 _SetStickyMode(sticky);
1387 int action;
1388 BMenuItem* menuItem = _Track(&action);
1390 fExtraRect = NULL;
1392 return menuItem;
1396 // #pragma mark - BMenu private methods
1399 bool
1400 BMenu::AddDynamicItem(add_state state)
1402 // Implemented in subclasses
1403 return false;
1407 void
1408 BMenu::DrawBackground(BRect updateRect)
1410 rgb_color base = ui_color(B_MENU_BACKGROUND_COLOR);
1411 uint32 flags = 0;
1412 if (!IsEnabled())
1413 flags |= BControlLook::B_DISABLED;
1415 if (IsFocus())
1416 flags |= BControlLook::B_FOCUSED;
1418 BRect rect = Bounds();
1419 uint32 borders = BControlLook::B_LEFT_BORDER
1420 | BControlLook::B_RIGHT_BORDER;
1421 if (Window() != NULL && Parent() != NULL) {
1422 if (Parent()->Frame().top == Window()->Bounds().top)
1423 borders |= BControlLook::B_TOP_BORDER;
1425 if (Parent()->Frame().bottom == Window()->Bounds().bottom)
1426 borders |= BControlLook::B_BOTTOM_BORDER;
1427 } else {
1428 borders |= BControlLook::B_TOP_BORDER
1429 | BControlLook::B_BOTTOM_BORDER;
1431 be_control_look->DrawMenuBackground(this, rect, updateRect, base, 0,
1432 borders);
1436 void
1437 BMenu::SetTrackingHook(menu_tracking_hook func, void* state)
1439 delete fExtraMenuData;
1440 fExtraMenuData = new (nothrow) BPrivate::ExtraMenuData(func, state);
1444 void BMenu::_ReservedMenu3() {}
1445 void BMenu::_ReservedMenu4() {}
1446 void BMenu::_ReservedMenu5() {}
1447 void BMenu::_ReservedMenu6() {}
1450 void
1451 BMenu::_InitData(BMessage* archive)
1453 BPrivate::kEmptyMenuLabel = B_TRANSLATE("<empty>");
1455 // TODO: Get _color, _fname, _fflt from the message, if present
1456 BFont font;
1457 font.SetFamilyAndStyle(sMenuInfo.f_family, sMenuInfo.f_style);
1458 font.SetSize(sMenuInfo.font_size);
1459 SetFont(&font, B_FONT_FAMILY_AND_STYLE | B_FONT_SIZE);
1461 fLayoutData = new LayoutData;
1462 fLayoutData->lastResizingMode = ResizingMode();
1464 SetLowUIColor(B_MENU_BACKGROUND_COLOR);
1465 SetViewColor(B_TRANSPARENT_COLOR);
1467 fTriggerEnabled = sMenuInfo.triggers_always_shown;
1469 if (archive != NULL) {
1470 archive->FindInt32("_layout", (int32*)&fLayout);
1471 archive->FindBool("_rsize_to_fit", &fResizeToFit);
1472 bool disabled;
1473 if (archive->FindBool("_disable", &disabled) == B_OK)
1474 fEnabled = !disabled;
1475 archive->FindBool("_radio", &fRadioMode);
1477 bool disableTrigger = false;
1478 archive->FindBool("_trig_disabled", &disableTrigger);
1479 fTriggerEnabled = !disableTrigger;
1481 archive->FindBool("_dyn_label", &fDynamicName);
1482 archive->FindFloat("_maxwidth", &fMaxContentWidth);
1484 BMessage msg;
1485 for (int32 i = 0; archive->FindMessage("_items", i, &msg) == B_OK; i++) {
1486 BArchivable* object = instantiate_object(&msg);
1487 if (BMenuItem* item = dynamic_cast<BMenuItem*>(object)) {
1488 BRect bounds;
1489 if (fLayout == B_ITEMS_IN_MATRIX
1490 && archive->FindRect("_i_frames", i, &bounds) == B_OK)
1491 AddItem(item, bounds);
1492 else
1493 AddItem(item);
1500 bool
1501 BMenu::_Show(bool selectFirstItem, bool keyDown)
1503 if (Window() != NULL)
1504 return false;
1506 // See if the supermenu has a cached menuwindow,
1507 // and use that one if possible.
1508 BMenuWindow* window = NULL;
1509 bool ourWindow = false;
1510 if (fSuper != NULL) {
1511 fSuperbounds = fSuper->ConvertToScreen(fSuper->Bounds());
1512 window = fSuper->_MenuWindow();
1515 // Otherwise, create a new one
1516 // This happens for "stand alone" BPopUpMenus
1517 // (i.e. not within a BMenuField)
1518 if (window == NULL) {
1519 // Menu windows get the BMenu's handler name
1520 window = new (nothrow) BMenuWindow(Name());
1521 ourWindow = true;
1524 if (window == NULL)
1525 return false;
1527 if (window->Lock()) {
1528 bool addAborted = false;
1529 if (keyDown)
1530 addAborted = _AddDynamicItems(keyDown);
1532 if (addAborted) {
1533 if (ourWindow)
1534 window->Quit();
1535 else
1536 window->Unlock();
1537 return false;
1539 fAttachAborted = false;
1541 window->AttachMenu(this);
1543 if (ItemAt(0) != NULL) {
1544 float width, height;
1545 ItemAt(0)->GetContentSize(&width, &height);
1547 window->SetSmallStep(ceilf(height));
1550 // Menu didn't have the time to add its items: aborting...
1551 if (fAttachAborted) {
1552 window->DetachMenu();
1553 // TODO: Probably not needed, we can just let _hide() quit the
1554 // window.
1555 if (ourWindow)
1556 window->Quit();
1557 else
1558 window->Unlock();
1559 return false;
1562 _UpdateWindowViewSize(true);
1563 window->Show();
1565 if (selectFirstItem)
1566 _SelectItem(ItemAt(0), false);
1568 window->Unlock();
1571 return true;
1575 void
1576 BMenu::_Hide()
1578 BMenuWindow* window = dynamic_cast<BMenuWindow*>(Window());
1579 if (window == NULL || !window->Lock())
1580 return;
1582 if (fSelected != NULL)
1583 _SelectItem(NULL);
1585 window->Hide();
1586 window->DetachMenu();
1587 // we don't want to be deleted when the window is removed
1589 #if USE_CACHED_MENUWINDOW
1590 if (fSuper != NULL)
1591 window->Unlock();
1592 else
1593 #endif
1594 window->Quit();
1595 // it's our window, quit it
1597 _DeleteMenuWindow();
1598 // Delete the menu window used by our submenus
1602 // #pragma mark - mouse tracking
1605 const static bigtime_t kOpenSubmenuDelay = 225000;
1606 const static bigtime_t kNavigationAreaTimeout = 1000000;
1609 BMenuItem*
1610 BMenu::_Track(int* action, long start)
1612 // TODO: cleanup
1613 BMenuItem* item = NULL;
1614 BRect navAreaRectAbove;
1615 BRect navAreaRectBelow;
1616 bigtime_t selectedTime = system_time();
1617 bigtime_t navigationAreaTime = 0;
1619 fState = MENU_STATE_TRACKING;
1620 fChosenItem = NULL;
1621 // we will use this for keyboard selection
1623 BPoint location;
1624 uint32 buttons = 0;
1625 if (LockLooper()) {
1626 GetMouse(&location, &buttons);
1627 UnlockLooper();
1630 bool releasedOnce = buttons == 0;
1631 while (fState != MENU_STATE_CLOSED) {
1632 if (_CustomTrackingWantsToQuit())
1633 break;
1635 if (!LockLooper())
1636 break;
1638 BMenuWindow* window = static_cast<BMenuWindow*>(Window());
1639 BPoint screenLocation = ConvertToScreen(location);
1640 if (window->CheckForScrolling(screenLocation)) {
1641 UnlockLooper();
1642 continue;
1645 // The order of the checks is important
1646 // to be able to handle overlapping menus:
1647 // first we check if mouse is inside a submenu,
1648 // then if the mouse is inside this menu,
1649 // then if it's over a super menu.
1650 if (_OverSubmenu(fSelected, screenLocation)
1651 || fState == MENU_STATE_KEY_TO_SUBMENU) {
1652 if (fState == MENU_STATE_TRACKING) {
1653 // not if from R.Arrow
1654 fState = MENU_STATE_TRACKING_SUBMENU;
1656 navAreaRectAbove = BRect();
1657 navAreaRectBelow = BRect();
1659 // Since the submenu has its own looper,
1660 // we can unlock ours. Doing so also make sure
1661 // that our window gets any update message to
1662 // redraw itself
1663 UnlockLooper();
1664 int submenuAction = MENU_STATE_TRACKING;
1665 BMenu* submenu = fSelected->Submenu();
1666 submenu->_SetStickyMode(_IsStickyMode());
1668 // The following call blocks until the submenu
1669 // gives control back to us, either because the mouse
1670 // pointer goes out of the submenu's bounds, or because
1671 // the user closes the menu
1672 BMenuItem* submenuItem = submenu->_Track(&submenuAction);
1673 if (submenuAction == MENU_STATE_CLOSED) {
1674 item = submenuItem;
1675 fState = MENU_STATE_CLOSED;
1676 } else if (submenuAction == MENU_STATE_KEY_LEAVE_SUBMENU) {
1677 if (LockLooper()) {
1678 BMenuItem* temp = fSelected;
1679 // close the submenu:
1680 _SelectItem(NULL);
1681 // but reselect the item itself for user:
1682 _SelectItem(temp, false);
1683 UnlockLooper();
1685 // cancel key-nav state
1686 fState = MENU_STATE_TRACKING;
1687 } else
1688 fState = MENU_STATE_TRACKING;
1689 if (!LockLooper())
1690 break;
1691 } else if ((item = _HitTestItems(location, B_ORIGIN)) != NULL) {
1692 _UpdateStateOpenSelect(item, location, navAreaRectAbove,
1693 navAreaRectBelow, selectedTime, navigationAreaTime);
1694 releasedOnce = true;
1695 } else if (_OverSuper(screenLocation)
1696 && fSuper->fState != MENU_STATE_KEY_TO_SUBMENU) {
1697 fState = MENU_STATE_TRACKING;
1698 UnlockLooper();
1699 break;
1700 } else if (fState == MENU_STATE_KEY_LEAVE_SUBMENU) {
1701 UnlockLooper();
1702 break;
1703 } else if (fSuper == NULL
1704 || fSuper->fState != MENU_STATE_KEY_TO_SUBMENU) {
1705 // Mouse pointer outside menu:
1706 // If there's no other submenu opened,
1707 // deselect the current selected item
1708 if (fSelected != NULL
1709 && (fSelected->Submenu() == NULL
1710 || fSelected->Submenu()->Window() == NULL)) {
1711 _SelectItem(NULL);
1712 fState = MENU_STATE_TRACKING;
1715 if (fSuper != NULL) {
1716 // Give supermenu the chance to continue tracking
1717 *action = fState;
1718 UnlockLooper();
1719 return NULL;
1723 UnlockLooper();
1725 if (releasedOnce)
1726 _UpdateStateClose(item, location, buttons);
1728 if (fState != MENU_STATE_CLOSED) {
1729 bigtime_t snoozeAmount = 50000;
1731 BPoint newLocation = location;
1732 uint32 newButtons = buttons;
1734 // If user doesn't move the mouse, loop here,
1735 // so we don't interfere with keyboard menu navigation
1736 do {
1737 snooze(snoozeAmount);
1738 if (!LockLooper())
1739 break;
1740 GetMouse(&newLocation, &newButtons, true);
1741 UnlockLooper();
1742 } while (newLocation == location && newButtons == buttons
1743 && !(item != NULL && item->Submenu() != NULL
1744 && item->Submenu()->Window() == NULL)
1745 && fState == MENU_STATE_TRACKING);
1747 if (newLocation != location || newButtons != buttons) {
1748 if (!releasedOnce && newButtons == 0 && buttons != 0)
1749 releasedOnce = true;
1750 location = newLocation;
1751 buttons = newButtons;
1754 if (releasedOnce)
1755 _UpdateStateClose(item, location, buttons);
1759 if (action != NULL)
1760 *action = fState;
1762 // keyboard Enter will set this
1763 if (fChosenItem != NULL)
1764 item = fChosenItem;
1765 else if (fSelected == NULL) {
1766 // needed to cover (rare) mouse/ESC combination
1767 item = NULL;
1770 if (fSelected != NULL && LockLooper()) {
1771 _SelectItem(NULL);
1772 UnlockLooper();
1775 // delete the menu window recycled for all the child menus
1776 _DeleteMenuWindow();
1778 return item;
1782 void
1783 BMenu::_UpdateNavigationArea(BPoint position, BRect& navAreaRectAbove,
1784 BRect& navAreaRectBelow)
1786 #define NAV_AREA_THRESHOLD 8
1788 // The navigation area is a region in which mouse-overs won't select
1789 // the item under the cursor. This makes it easier to navigate to
1790 // submenus, as the cursor can be moved to submenu items directly instead
1791 // of having to move it horizontally into the submenu first. The concept
1792 // is illustrated below:
1794 // +-------+----+---------+
1795 // | | /| |
1796 // | | /*| |
1797 // |[2]--> | /**| |
1798 // | |/[4]| |
1799 // |------------| |
1800 // | [1] | [6] |
1801 // |------------| |
1802 // | |\[5]| |
1803 // |[3]--> | \**| |
1804 // | | \*| |
1805 // | | \| |
1806 // | +----|---------+
1807 // | |
1808 // +------------+
1810 // [1] Selected item, cursor position ('position')
1811 // [2] Upper navigation area rectangle ('navAreaRectAbove')
1812 // [3] Lower navigation area rectangle ('navAreaRectBelow')
1813 // [4] Upper navigation area
1814 // [5] Lower navigation area
1815 // [6] Submenu
1817 // The rectangles are used to calculate if the cursor is in the actual
1818 // navigation area (see _UpdateStateOpenSelect()).
1820 if (fSelected == NULL)
1821 return;
1823 BMenu* submenu = fSelected->Submenu();
1825 if (submenu != NULL) {
1826 BRect menuBounds = ConvertToScreen(Bounds());
1828 BRect submenuBounds;
1829 if (fSelected->Submenu()->LockLooper()) {
1830 submenuBounds = fSelected->Submenu()->ConvertToScreen(
1831 fSelected->Submenu()->Bounds());
1832 fSelected->Submenu()->UnlockLooper();
1835 if (menuBounds.left < submenuBounds.left) {
1836 navAreaRectAbove.Set(position.x + NAV_AREA_THRESHOLD,
1837 submenuBounds.top, menuBounds.right,
1838 position.y);
1839 navAreaRectBelow.Set(position.x + NAV_AREA_THRESHOLD,
1840 position.y, menuBounds.right,
1841 submenuBounds.bottom);
1842 } else {
1843 navAreaRectAbove.Set(menuBounds.left,
1844 submenuBounds.top, position.x - NAV_AREA_THRESHOLD,
1845 position.y);
1846 navAreaRectBelow.Set(menuBounds.left,
1847 position.y, position.x - NAV_AREA_THRESHOLD,
1848 submenuBounds.bottom);
1850 } else {
1851 navAreaRectAbove = BRect();
1852 navAreaRectBelow = BRect();
1857 void
1858 BMenu::_UpdateStateOpenSelect(BMenuItem* item, BPoint position,
1859 BRect& navAreaRectAbove, BRect& navAreaRectBelow, bigtime_t& selectedTime,
1860 bigtime_t& navigationAreaTime)
1862 if (fState == MENU_STATE_CLOSED)
1863 return;
1865 if (item != fSelected) {
1866 if (navigationAreaTime == 0)
1867 navigationAreaTime = system_time();
1869 position = ConvertToScreen(position);
1871 bool inNavAreaRectAbove = navAreaRectAbove.Contains(position);
1872 bool inNavAreaRectBelow = navAreaRectBelow.Contains(position);
1874 if (fSelected == NULL
1875 || (!inNavAreaRectAbove && !inNavAreaRectBelow)) {
1876 _SelectItem(item, false);
1877 navAreaRectAbove = BRect();
1878 navAreaRectBelow = BRect();
1879 selectedTime = system_time();
1880 navigationAreaTime = 0;
1881 return;
1884 BRect menuBounds = ConvertToScreen(Bounds());
1886 BRect submenuBounds;
1887 if (fSelected->Submenu()->LockLooper()) {
1888 fSelected->Submenu()->ConvertToScreen(
1889 fSelected->Submenu()->Bounds());
1890 fSelected->Submenu()->UnlockLooper();
1893 float xOffset;
1895 // navAreaRectAbove and navAreaRectBelow have the same X
1896 // position and width, so it doesn't matter which one we use to
1897 // calculate the X offset
1898 if (menuBounds.left < submenuBounds.left)
1899 xOffset = position.x - navAreaRectAbove.left;
1900 else
1901 xOffset = navAreaRectAbove.right - position.x;
1903 bool inNavArea;
1905 if (inNavAreaRectAbove) {
1906 float yOffset = navAreaRectAbove.bottom - position.y;
1907 float ratio = navAreaRectAbove.Width() / navAreaRectAbove.Height();
1909 inNavArea = yOffset <= xOffset / ratio;
1910 } else {
1911 float yOffset = navAreaRectBelow.bottom - position.y;
1912 float ratio = navAreaRectBelow.Width() / navAreaRectBelow.Height();
1914 inNavArea = yOffset >= (navAreaRectBelow.Height() - xOffset
1915 / ratio);
1918 bigtime_t systime = system_time();
1920 if (!inNavArea || (navigationAreaTime > 0 && systime -
1921 navigationAreaTime > kNavigationAreaTimeout)) {
1922 // Don't delay opening of submenu if the user had
1923 // to wait for the navigation area timeout anyway
1924 _SelectItem(item, inNavArea);
1926 if (inNavArea) {
1927 _UpdateNavigationArea(position, navAreaRectAbove,
1928 navAreaRectBelow);
1929 } else {
1930 navAreaRectAbove = BRect();
1931 navAreaRectBelow = BRect();
1934 selectedTime = system_time();
1935 navigationAreaTime = 0;
1937 } else if (fSelected->Submenu() != NULL &&
1938 system_time() - selectedTime > kOpenSubmenuDelay) {
1939 _SelectItem(fSelected, true);
1941 if (!navAreaRectAbove.IsValid() && !navAreaRectBelow.IsValid()) {
1942 position = ConvertToScreen(position);
1943 _UpdateNavigationArea(position, navAreaRectAbove,
1944 navAreaRectBelow);
1948 if (fState != MENU_STATE_TRACKING)
1949 fState = MENU_STATE_TRACKING;
1953 void
1954 BMenu::_UpdateStateClose(BMenuItem* item, const BPoint& where,
1955 const uint32& buttons)
1957 if (fState == MENU_STATE_CLOSED)
1958 return;
1960 if (buttons != 0 && _IsStickyMode()) {
1961 if (item == NULL) {
1962 if (item != fSelected && LockLooper()) {
1963 _SelectItem(item, false);
1964 UnlockLooper();
1966 fState = MENU_STATE_CLOSED;
1967 } else
1968 _SetStickyMode(false);
1969 } else if (buttons == 0 && !_IsStickyMode()) {
1970 if (fExtraRect != NULL && fExtraRect->Contains(where)) {
1971 _SetStickyMode(true);
1972 fExtraRect = NULL;
1973 // Setting this to NULL will prevent this code
1974 // to be executed next time
1975 } else {
1976 if (item != fSelected && LockLooper()) {
1977 _SelectItem(item, false);
1978 UnlockLooper();
1980 fState = MENU_STATE_CLOSED;
1986 bool
1987 BMenu::_AddItem(BMenuItem* item, int32 index)
1989 ASSERT(item != NULL);
1990 if (index < 0 || index > fItems.CountItems())
1991 return false;
1993 if (item->IsMarked())
1994 _ItemMarked(item);
1996 if (!fItems.AddItem(item, index))
1997 return false;
1999 // install the item on the supermenu's window
2000 // or onto our window, if we are a root menu
2001 BWindow* window = NULL;
2002 if (Superitem() != NULL)
2003 window = Superitem()->fWindow;
2004 else
2005 window = Window();
2006 if (window != NULL)
2007 item->Install(window);
2009 item->SetSuper(this);
2010 return true;
2014 bool
2015 BMenu::_RemoveItems(int32 index, int32 count, BMenuItem* item,
2016 bool deleteItems)
2018 bool success = false;
2019 bool invalidateLayout = false;
2021 bool locked = LockLooper();
2022 BWindow* window = Window();
2024 // The plan is simple: If we're given a BMenuItem directly, we use it
2025 // and ignore index and count. Otherwise, we use them instead.
2026 if (item != NULL) {
2027 if (fItems.RemoveItem(item)) {
2028 if (item == fSelected && window != NULL)
2029 _SelectItem(NULL);
2030 item->Uninstall();
2031 item->SetSuper(NULL);
2032 if (deleteItems)
2033 delete item;
2034 success = invalidateLayout = true;
2036 } else {
2037 // We iterate backwards because it's simpler
2038 int32 i = std::min(index + count - 1, fItems.CountItems() - 1);
2039 // NOTE: the range check for "index" is done after
2040 // calculating the last index to be removed, so
2041 // that the range is not "shifted" unintentionally
2042 index = std::max((int32)0, index);
2043 for (; i >= index; i--) {
2044 item = static_cast<BMenuItem*>(fItems.ItemAt(i));
2045 if (item != NULL) {
2046 if (fItems.RemoveItem(item)) {
2047 if (item == fSelected && window != NULL)
2048 _SelectItem(NULL);
2049 item->Uninstall();
2050 item->SetSuper(NULL);
2051 if (deleteItems)
2052 delete item;
2053 success = true;
2054 invalidateLayout = true;
2055 } else {
2056 // operation not entirely successful
2057 success = false;
2058 break;
2064 if (invalidateLayout) {
2065 InvalidateLayout();
2066 if (locked && window != NULL) {
2067 _LayoutItems(0);
2068 _UpdateWindowViewSize(false);
2069 Invalidate();
2073 if (locked)
2074 UnlockLooper();
2076 return success;
2080 bool
2081 BMenu::_RelayoutIfNeeded()
2083 if (!fUseCachedMenuLayout) {
2084 fUseCachedMenuLayout = true;
2085 _CacheFontInfo();
2086 _LayoutItems(0);
2087 return true;
2089 return false;
2093 void
2094 BMenu::_LayoutItems(int32 index)
2096 _CalcTriggers();
2098 float width;
2099 float height;
2100 _ComputeLayout(index, fResizeToFit, true, &width, &height);
2102 if (fResizeToFit)
2103 ResizeTo(width, height);
2107 BSize
2108 BMenu::_ValidatePreferredSize()
2110 if (!fLayoutData->preferred.IsWidthSet() || ResizingMode()
2111 != fLayoutData->lastResizingMode) {
2112 _ComputeLayout(0, true, false, NULL, NULL);
2113 ResetLayoutInvalidation();
2116 return fLayoutData->preferred;
2120 void
2121 BMenu::_ComputeLayout(int32 index, bool bestFit, bool moveItems,
2122 float* _width, float* _height)
2124 // TODO: Take "bestFit", "moveItems", "index" into account,
2125 // Recalculate only the needed items,
2126 // not the whole layout every time
2128 fLayoutData->lastResizingMode = ResizingMode();
2130 BRect frame;
2131 switch (fLayout) {
2132 case B_ITEMS_IN_COLUMN:
2134 BRect parentFrame;
2135 BRect* overrideFrame = NULL;
2136 if (dynamic_cast<_BMCMenuBar_*>(Supermenu()) != NULL) {
2137 // When the menu is modified while it's open, we get here in a
2138 // situation where trying to lock the looper would deadlock
2139 // (the window is locked waiting for the menu to terminate).
2140 // In that case, just give up on getting the supermenu bounds
2141 // and keep the menu at the current width and position.
2142 if (Supermenu()->LockLooperWithTimeout(0) == B_OK) {
2143 parentFrame = Supermenu()->Bounds();
2144 Supermenu()->UnlockLooper();
2145 overrideFrame = &parentFrame;
2149 _ComputeColumnLayout(index, bestFit, moveItems, overrideFrame,
2150 frame);
2151 break;
2154 case B_ITEMS_IN_ROW:
2155 _ComputeRowLayout(index, bestFit, moveItems, frame);
2156 break;
2158 case B_ITEMS_IN_MATRIX:
2159 _ComputeMatrixLayout(frame);
2160 break;
2163 // change width depending on resize mode
2164 BSize size;
2165 if ((ResizingMode() & B_FOLLOW_LEFT_RIGHT) == B_FOLLOW_LEFT_RIGHT) {
2166 if (dynamic_cast<_BMCMenuBar_*>(this) != NULL)
2167 size.width = Bounds().Width() - fPad.right;
2168 else if (Parent() != NULL)
2169 size.width = Parent()->Frame().Width() + 1;
2170 else if (Window() != NULL)
2171 size.width = Window()->Frame().Width() + 1;
2172 else
2173 size.width = Bounds().Width();
2174 } else
2175 size.width = frame.Width();
2177 size.height = frame.Height();
2179 if (_width)
2180 *_width = size.width;
2182 if (_height)
2183 *_height = size.height;
2185 if (bestFit)
2186 fLayoutData->preferred = size;
2188 if (moveItems)
2189 fUseCachedMenuLayout = true;
2193 void
2194 BMenu::_ComputeColumnLayout(int32 index, bool bestFit, bool moveItems,
2195 BRect* overrideFrame, BRect& frame)
2197 bool command = false;
2198 bool control = false;
2199 bool shift = false;
2200 bool option = false;
2202 if (index > 0)
2203 frame = ItemAt(index - 1)->Frame();
2204 else if (overrideFrame != NULL)
2205 frame.Set(0, 0, overrideFrame->right, -1);
2206 else
2207 frame.Set(0, 0, 0, -1);
2209 BFont font;
2210 GetFont(&font);
2212 for (; index < fItems.CountItems(); index++) {
2213 BMenuItem* item = ItemAt(index);
2215 float width;
2216 float height;
2217 item->GetContentSize(&width, &height);
2219 if (item->fModifiers && item->fShortcutChar) {
2220 width += font.Size();
2221 if ((item->fModifiers & B_COMMAND_KEY) != 0)
2222 command = true;
2224 if ((item->fModifiers & B_CONTROL_KEY) != 0)
2225 control = true;
2227 if ((item->fModifiers & B_SHIFT_KEY) != 0)
2228 shift = true;
2230 if ((item->fModifiers & B_OPTION_KEY) != 0)
2231 option = true;
2234 item->fBounds.left = 0.0f;
2235 item->fBounds.top = frame.bottom + 1.0f;
2236 item->fBounds.bottom = item->fBounds.top + height + fPad.top
2237 + fPad.bottom;
2239 if (item->fSubmenu != NULL)
2240 width += item->Frame().Height();
2242 frame.right = std::max(frame.right, width + fPad.left + fPad.right);
2243 frame.bottom = item->fBounds.bottom;
2246 if (command) {
2247 frame.right
2248 += BPrivate::MenuPrivate::MenuItemCommand()->Bounds().Width() + 1;
2250 if (control) {
2251 frame.right
2252 += BPrivate::MenuPrivate::MenuItemControl()->Bounds().Width() + 1;
2254 if (option) {
2255 frame.right
2256 += BPrivate::MenuPrivate::MenuItemOption()->Bounds().Width() + 1;
2258 if (shift) {
2259 frame.right
2260 += BPrivate::MenuPrivate::MenuItemShift()->Bounds().Width() + 1;
2263 if (fMaxContentWidth > 0)
2264 frame.right = std::min(frame.right, fMaxContentWidth);
2266 if (moveItems) {
2267 for (int32 i = 0; i < fItems.CountItems(); i++)
2268 ItemAt(i)->fBounds.right = frame.right;
2271 frame.top = 0;
2272 frame.right = ceilf(frame.right);
2276 void
2277 BMenu::_ComputeRowLayout(int32 index, bool bestFit, bool moveItems,
2278 BRect& frame)
2280 font_height fh;
2281 GetFontHeight(&fh);
2282 frame.Set(0.0f, 0.0f, 0.0f, ceilf(fh.ascent + fh.descent + fPad.top
2283 + fPad.bottom));
2285 for (int32 i = 0; i < fItems.CountItems(); i++) {
2286 BMenuItem* item = ItemAt(i);
2288 float width, height;
2289 item->GetContentSize(&width, &height);
2291 item->fBounds.left = frame.right;
2292 item->fBounds.top = 0.0f;
2293 item->fBounds.right = item->fBounds.left + width + fPad.left
2294 + fPad.right;
2296 frame.right = item->Frame().right + 1.0f;
2297 frame.bottom = std::max(frame.bottom, height + fPad.top + fPad.bottom);
2300 if (moveItems) {
2301 for (int32 i = 0; i < fItems.CountItems(); i++)
2302 ItemAt(i)->fBounds.bottom = frame.bottom;
2305 if (bestFit)
2306 frame.right = ceilf(frame.right);
2307 else
2308 frame.right = Bounds().right;
2312 void
2313 BMenu::_ComputeMatrixLayout(BRect &frame)
2315 frame.Set(0, 0, 0, 0);
2316 for (int32 i = 0; i < CountItems(); i++) {
2317 BMenuItem* item = ItemAt(i);
2318 if (item != NULL) {
2319 frame.left = std::min(frame.left, item->Frame().left);
2320 frame.right = std::max(frame.right, item->Frame().right);
2321 frame.top = std::min(frame.top, item->Frame().top);
2322 frame.bottom = std::max(frame.bottom, item->Frame().bottom);
2328 void
2329 BMenu::LayoutInvalidated(bool descendants)
2331 fUseCachedMenuLayout = false;
2332 fLayoutData->preferred.Set(B_SIZE_UNSET, B_SIZE_UNSET);
2336 // Assumes the SuperMenu to be locked (due to calling ConvertToScreen())
2337 BPoint
2338 BMenu::ScreenLocation()
2340 BMenu* superMenu = Supermenu();
2341 BMenuItem* superItem = Superitem();
2343 if (superMenu == NULL || superItem == NULL) {
2344 debugger("BMenu can't determine where to draw."
2345 "Override BMenu::ScreenLocation() to determine location.");
2348 BPoint point;
2349 if (superMenu->Layout() == B_ITEMS_IN_COLUMN)
2350 point = superItem->Frame().RightTop() + BPoint(1.0f, 1.0f);
2351 else
2352 point = superItem->Frame().LeftBottom() + BPoint(1.0f, 1.0f);
2354 superMenu->ConvertToScreen(&point);
2356 return point;
2360 BRect
2361 BMenu::_CalcFrame(BPoint where, bool* scrollOn)
2363 // TODO: Improve me
2364 BRect bounds = Bounds();
2365 BRect frame = bounds.OffsetToCopy(where);
2367 BScreen screen(Window());
2368 BRect screenFrame = screen.Frame();
2370 BMenu* superMenu = Supermenu();
2371 BMenuItem* superItem = Superitem();
2373 // TODO: Horrible hack:
2374 // When added to a BMenuField, a BPopUpMenu is the child of
2375 // a _BMCMenuBar_ to "fake" the menu hierarchy
2376 bool inMenuField = dynamic_cast<_BMCMenuBar_*>(superMenu) != NULL;
2378 // Offset the menu field menu window left by the width of the checkmark
2379 // so that the text when the menu is closed lines up with the text when
2380 // the menu is open.
2381 if (inMenuField)
2382 frame.OffsetBy(-8.0f, 0.0f);
2384 bool scroll = false;
2385 if (superMenu == NULL || superItem == NULL || inMenuField) {
2386 // just move the window on screen
2388 if (frame.bottom > screenFrame.bottom)
2389 frame.OffsetBy(0, screenFrame.bottom - frame.bottom);
2390 else if (frame.top < screenFrame.top)
2391 frame.OffsetBy(0, -frame.top);
2393 if (frame.right > screenFrame.right)
2394 frame.OffsetBy(screenFrame.right - frame.right, 0);
2395 else if (frame.left < screenFrame.left)
2396 frame.OffsetBy(-frame.left, 0);
2397 } else if (superMenu->Layout() == B_ITEMS_IN_COLUMN) {
2398 if (frame.right > screenFrame.right)
2399 frame.OffsetBy(-superItem->Frame().Width() - frame.Width() - 2, 0);
2401 if (frame.left < 0)
2402 frame.OffsetBy(-frame.left + 6, 0);
2404 if (frame.bottom > screenFrame.bottom)
2405 frame.OffsetBy(0, screenFrame.bottom - frame.bottom);
2406 } else {
2407 if (frame.bottom > screenFrame.bottom) {
2408 if (scrollOn != NULL && superMenu != NULL
2409 && dynamic_cast<BMenuBar*>(superMenu) != NULL
2410 && frame.top < (screenFrame.bottom - 80)) {
2411 scroll = true;
2412 } else {
2413 frame.OffsetBy(0, -superItem->Frame().Height()
2414 - frame.Height() - 3);
2418 if (frame.right > screenFrame.right)
2419 frame.OffsetBy(screenFrame.right - frame.right, 0);
2422 if (!scroll) {
2423 // basically, if this returns false, it means
2424 // that the menu frame won't fit completely inside the screen
2425 // TODO: Scrolling will currently only work up/down,
2426 // not left/right
2427 scroll = screenFrame.Height() < frame.Height();
2430 if (scrollOn != NULL)
2431 *scrollOn = scroll;
2433 return frame;
2437 void
2438 BMenu::DrawItems(BRect updateRect)
2440 int32 itemCount = fItems.CountItems();
2441 for (int32 i = 0; i < itemCount; i++) {
2442 BMenuItem* item = ItemAt(i);
2443 if (item->Frame().Intersects(updateRect))
2444 item->Draw();
2450 BMenu::_State(BMenuItem** item) const
2452 if (fState == MENU_STATE_TRACKING || fState == MENU_STATE_CLOSED)
2453 return fState;
2455 if (fSelected != NULL && fSelected->Submenu() != NULL)
2456 return fSelected->Submenu()->_State(item);
2458 return fState;
2462 void
2463 BMenu::_InvokeItem(BMenuItem* item, bool now)
2465 if (!item->IsEnabled())
2466 return;
2468 // Do the "selected" animation
2469 // TODO: Doesn't work. This is supposed to highlight
2470 // and dehighlight the item, works on beos but not on haiku.
2471 if (!item->Submenu() && LockLooper()) {
2472 snooze(50000);
2473 item->Select(true);
2474 Window()->UpdateIfNeeded();
2475 snooze(50000);
2476 item->Select(false);
2477 Window()->UpdateIfNeeded();
2478 snooze(50000);
2479 item->Select(true);
2480 Window()->UpdateIfNeeded();
2481 snooze(50000);
2482 item->Select(false);
2483 Window()->UpdateIfNeeded();
2484 UnlockLooper();
2487 // Lock the root menu window before calling BMenuItem::Invoke()
2488 BMenu* parent = this;
2489 BMenu* rootMenu = NULL;
2490 do {
2491 rootMenu = parent;
2492 parent = rootMenu->Supermenu();
2493 } while (parent != NULL);
2495 if (rootMenu->LockLooper()) {
2496 item->Invoke();
2497 rootMenu->UnlockLooper();
2502 bool
2503 BMenu::_OverSuper(BPoint location)
2505 if (!Supermenu())
2506 return false;
2508 return fSuperbounds.Contains(location);
2512 bool
2513 BMenu::_OverSubmenu(BMenuItem* item, BPoint loc)
2515 if (item == NULL)
2516 return false;
2518 BMenu* subMenu = item->Submenu();
2519 if (subMenu == NULL || subMenu->Window() == NULL)
2520 return false;
2522 // assume that loc is in screen coordinates
2523 if (subMenu->Window()->Frame().Contains(loc))
2524 return true;
2526 return subMenu->_OverSubmenu(subMenu->fSelected, loc);
2530 BMenuWindow*
2531 BMenu::_MenuWindow()
2533 #if USE_CACHED_MENUWINDOW
2534 if (fCachedMenuWindow == NULL) {
2535 char windowName[64];
2536 snprintf(windowName, 64, "%s cached menu", Name());
2537 fCachedMenuWindow = new (nothrow) BMenuWindow(windowName);
2539 #endif
2540 return fCachedMenuWindow;
2544 void
2545 BMenu::_DeleteMenuWindow()
2547 if (fCachedMenuWindow != NULL) {
2548 fCachedMenuWindow->Lock();
2549 fCachedMenuWindow->Quit();
2550 fCachedMenuWindow = NULL;
2555 BMenuItem*
2556 BMenu::_HitTestItems(BPoint where, BPoint slop) const
2558 // TODO: Take "slop" into account ?
2560 // if the point doesn't lie within the menu's
2561 // bounds, bail out immediately
2562 if (!Bounds().Contains(where))
2563 return NULL;
2565 int32 itemCount = CountItems();
2566 for (int32 i = 0; i < itemCount; i++) {
2567 BMenuItem* item = ItemAt(i);
2568 if (item->Frame().Contains(where)
2569 && dynamic_cast<BSeparatorItem*>(item) == NULL) {
2570 return item;
2574 return NULL;
2578 BRect
2579 BMenu::_Superbounds() const
2581 return fSuperbounds;
2585 void
2586 BMenu::_CacheFontInfo()
2588 font_height fh;
2589 GetFontHeight(&fh);
2590 fAscent = fh.ascent;
2591 fDescent = fh.descent;
2592 fFontHeight = ceilf(fh.ascent + fh.descent + fh.leading);
2596 void
2597 BMenu::_ItemMarked(BMenuItem* item)
2599 if (IsRadioMode()) {
2600 for (int32 i = 0; i < CountItems(); i++) {
2601 if (ItemAt(i) != item)
2602 ItemAt(i)->SetMarked(false);
2606 if (IsLabelFromMarked() && Superitem() != NULL)
2607 Superitem()->SetLabel(item->Label());
2611 void
2612 BMenu::_Install(BWindow* target)
2614 for (int32 i = 0; i < CountItems(); i++)
2615 ItemAt(i)->Install(target);
2619 void
2620 BMenu::_Uninstall()
2622 for (int32 i = 0; i < CountItems(); i++)
2623 ItemAt(i)->Uninstall();
2627 void
2628 BMenu::_SelectItem(BMenuItem* item, bool showSubmenu, bool selectFirstItem,
2629 bool keyDown)
2631 // Avoid deselecting and then reselecting the same item
2632 // which would cause flickering
2633 if (item != fSelected) {
2634 if (fSelected != NULL) {
2635 fSelected->Select(false);
2636 BMenu* subMenu = fSelected->Submenu();
2637 if (subMenu != NULL && subMenu->Window() != NULL)
2638 subMenu->_Hide();
2641 fSelected = item;
2642 if (fSelected != NULL)
2643 fSelected->Select(true);
2646 if (fSelected != NULL && showSubmenu) {
2647 BMenu* subMenu = fSelected->Submenu();
2648 if (subMenu != NULL && subMenu->Window() == NULL) {
2649 if (!subMenu->_Show(selectFirstItem, keyDown)) {
2650 // something went wrong, deselect the item
2651 fSelected->Select(false);
2652 fSelected = NULL;
2659 bool
2660 BMenu::_SelectNextItem(BMenuItem* item, bool forward)
2662 if (CountItems() == 0) // cannot select next item in an empty menu
2663 return false;
2665 BMenuItem* nextItem = _NextItem(item, forward);
2666 if (nextItem == NULL)
2667 return false;
2669 _SelectItem(nextItem, dynamic_cast<BMenuBar*>(this) != NULL);
2671 if (LockLooper()) {
2672 be_app->ObscureCursor();
2673 UnlockLooper();
2676 return true;
2680 BMenuItem*
2681 BMenu::_NextItem(BMenuItem* item, bool forward) const
2683 const int32 numItems = fItems.CountItems();
2684 if (numItems == 0)
2685 return NULL;
2687 int32 index = fItems.IndexOf(item);
2688 int32 loopCount = numItems;
2689 while (--loopCount) {
2690 // Cycle through menu items in the given direction...
2691 if (forward)
2692 index++;
2693 else
2694 index--;
2696 // ... wrap around...
2697 if (index < 0)
2698 index = numItems - 1;
2699 else if (index >= numItems)
2700 index = 0;
2702 // ... and return the first suitable item found.
2703 BMenuItem* nextItem = ItemAt(index);
2704 if (nextItem->IsEnabled())
2705 return nextItem;
2708 // If no other suitable item was found, return NULL.
2709 return NULL;
2713 void
2714 BMenu::_SetStickyMode(bool sticky)
2716 if (fStickyMode == sticky)
2717 return;
2719 fStickyMode = sticky;
2721 if (fSuper != NULL) {
2722 // propagate the status to the super menu
2723 fSuper->_SetStickyMode(sticky);
2724 } else {
2725 // TODO: Ugly hack, but it needs to be done in this method
2726 BMenuBar* menuBar = dynamic_cast<BMenuBar*>(this);
2727 if (sticky && menuBar != NULL && menuBar->LockLooper()) {
2728 // If we are switching to sticky mode,
2729 // steal the focus from the current focus view
2730 // (needed to handle keyboard navigation)
2731 menuBar->_StealFocus();
2732 menuBar->UnlockLooper();
2738 bool
2739 BMenu::_IsStickyMode() const
2741 return fStickyMode;
2745 void
2746 BMenu::_GetShiftKey(uint32 &value) const
2748 // TODO: Move into init_interface_kit().
2749 // Currently we can't do that, as get_modifier_key() blocks forever
2750 // when called on input_server initialization, since it tries
2751 // to send a synchronous message to itself (input_server is
2752 // a BApplication)
2754 if (get_modifier_key(B_LEFT_SHIFT_KEY, &value) != B_OK)
2755 value = 0x4b;
2759 void
2760 BMenu::_GetControlKey(uint32 &value) const
2762 // TODO: Move into init_interface_kit().
2763 // Currently we can't do that, as get_modifier_key() blocks forever
2764 // when called on input_server initialization, since it tries
2765 // to send a synchronous message to itself (input_server is
2766 // a BApplication)
2768 if (get_modifier_key(B_LEFT_CONTROL_KEY, &value) != B_OK)
2769 value = 0x5c;
2773 void
2774 BMenu::_GetCommandKey(uint32 &value) const
2776 // TODO: Move into init_interface_kit().
2777 // Currently we can't do that, as get_modifier_key() blocks forever
2778 // when called on input_server initialization, since it tries
2779 // to send a synchronous message to itself (input_server is
2780 // a BApplication)
2782 if (get_modifier_key(B_LEFT_COMMAND_KEY, &value) != B_OK)
2783 value = 0x66;
2787 void
2788 BMenu::_GetOptionKey(uint32 &value) const
2790 // TODO: Move into init_interface_kit().
2791 // Currently we can't do that, as get_modifier_key() blocks forever
2792 // when called on input_server initialization, since it tries
2793 // to send a synchronous message to itself (input_server is
2794 // a BApplication)
2796 if (get_modifier_key(B_LEFT_OPTION_KEY, &value) != B_OK)
2797 value = 0x5d;
2801 void
2802 BMenu::_GetMenuKey(uint32 &value) const
2804 // TODO: Move into init_interface_kit().
2805 // Currently we can't do that, as get_modifier_key() blocks forever
2806 // when called on input_server initialization, since it tries
2807 // to send a synchronous message to itself (input_server is
2808 // a BApplication)
2810 if (get_modifier_key(B_MENU_KEY, &value) != B_OK)
2811 value = 0x68;
2815 void
2816 BMenu::_CalcTriggers()
2818 BPrivate::TriggerList triggerList;
2820 // Gathers the existing triggers set by the user
2821 for (int32 i = 0; i < CountItems(); i++) {
2822 char trigger = ItemAt(i)->Trigger();
2823 if (trigger != 0)
2824 triggerList.AddTrigger(trigger);
2827 // Set triggers for items which don't have one yet
2828 for (int32 i = 0; i < CountItems(); i++) {
2829 BMenuItem* item = ItemAt(i);
2830 if (item->Trigger() == 0) {
2831 uint32 trigger;
2832 int32 index;
2833 if (_ChooseTrigger(item->Label(), index, trigger, triggerList))
2834 item->SetAutomaticTrigger(index, trigger);
2840 bool
2841 BMenu::_ChooseTrigger(const char* title, int32& index, uint32& trigger,
2842 BPrivate::TriggerList& triggers)
2844 if (title == NULL)
2845 return false;
2847 uint32 c;
2849 // two runs: first we look out for uppercase letters
2850 // TODO: support Unicode characters correctly!
2851 for (uint32 i = 0; (c = title[i]) != '\0'; i++) {
2852 if (!IsInsideGlyph(c) && isupper(c) && !triggers.HasTrigger(c)) {
2853 index = i;
2854 trigger = tolower(c);
2855 return triggers.AddTrigger(c);
2859 // then, if we still haven't found anything, we accept them all
2860 index = 0;
2861 while ((c = UTF8ToCharCode(&title)) != 0) {
2862 if (!isspace(c) && !triggers.HasTrigger(c)) {
2863 trigger = tolower(c);
2864 return triggers.AddTrigger(c);
2867 index++;
2870 return false;
2874 void
2875 BMenu::_UpdateWindowViewSize(const bool &move)
2877 BMenuWindow* window = static_cast<BMenuWindow*>(Window());
2878 if (window == NULL)
2879 return;
2881 if (dynamic_cast<BMenuBar*>(this) != NULL)
2882 return;
2884 if (!fResizeToFit)
2885 return;
2887 bool scroll = false;
2888 const BPoint screenLocation = move ? ScreenLocation()
2889 : window->Frame().LeftTop();
2890 BRect frame = _CalcFrame(screenLocation, &scroll);
2891 ResizeTo(frame.Width(), frame.Height());
2893 if (fItems.CountItems() > 0) {
2894 if (!scroll) {
2895 window->ResizeTo(Bounds().Width(), Bounds().Height());
2896 } else {
2897 BScreen screen(window);
2899 // If we need scrolling, resize the window to fit the screen and
2900 // attach scrollers to our cached BMenuWindow.
2901 if (dynamic_cast<BMenuBar*>(Supermenu()) == NULL || frame.top < 0) {
2902 window->ResizeTo(Bounds().Width(), screen.Frame().Height());
2903 frame.top = 0;
2904 } else {
2905 // Or, in case our parent was a BMenuBar enable scrolling with
2906 // normal size.
2907 window->ResizeTo(Bounds().Width(),
2908 screen.Frame().bottom - frame.top);
2911 if (fLayout == B_ITEMS_IN_COLUMN) {
2912 // we currently only support scrolling for B_ITEMS_IN_COLUMN
2913 window->AttachScrollers();
2915 BMenuItem* selectedItem = FindMarked();
2916 if (selectedItem != NULL) {
2917 // scroll to the selected item
2918 if (Supermenu() == NULL) {
2919 window->TryScrollTo(selectedItem->Frame().top);
2920 } else {
2921 BPoint point = selectedItem->Frame().LeftTop();
2922 BPoint superPoint = Superitem()->Frame().LeftTop();
2923 Supermenu()->ConvertToScreen(&superPoint);
2924 ConvertToScreen(&point);
2925 window->TryScrollTo(point.y - superPoint.y);
2930 } else {
2931 _CacheFontInfo();
2932 window->ResizeTo(StringWidth(BPrivate::kEmptyMenuLabel)
2933 + fPad.left + fPad.right,
2934 fFontHeight + fPad.top + fPad.bottom);
2937 if (move)
2938 window->MoveTo(frame.LeftTop());
2942 bool
2943 BMenu::_AddDynamicItems(bool keyDown)
2945 bool addAborted = false;
2946 if (AddDynamicItem(B_INITIAL_ADD)) {
2947 BMenuItem* superItem = Superitem();
2948 BMenu* superMenu = Supermenu();
2949 do {
2950 if (superMenu != NULL
2951 && !superMenu->_OkToProceed(superItem, keyDown)) {
2952 AddDynamicItem(B_ABORT);
2953 addAborted = true;
2954 break;
2956 } while (AddDynamicItem(B_PROCESSING));
2959 return addAborted;
2963 bool
2964 BMenu::_OkToProceed(BMenuItem* item, bool keyDown)
2966 BPoint where;
2967 uint32 buttons;
2968 GetMouse(&where, &buttons, false);
2969 bool stickyMode = _IsStickyMode();
2970 // Quit if user clicks the mouse button in sticky mode
2971 // or releases the mouse button in nonsticky mode
2972 // or moves the pointer over another item
2973 // TODO: I added the check for BMenuBar to solve a problem with Deskbar.
2974 // BeOS seems to do something similar. This could also be a bug in
2975 // Deskbar, though.
2976 if ((buttons != 0 && stickyMode)
2977 || ((dynamic_cast<BMenuBar*>(this) == NULL
2978 && (buttons == 0 && !stickyMode))
2979 || ((_HitTestItems(where) != item) && !keyDown))) {
2980 return false;
2983 return true;
2987 bool
2988 BMenu::_CustomTrackingWantsToQuit()
2990 if (fExtraMenuData != NULL && fExtraMenuData->trackingHook != NULL
2991 && fExtraMenuData->trackingState != NULL) {
2992 return fExtraMenuData->trackingHook(this,
2993 fExtraMenuData->trackingState);
2996 return false;
3000 void
3001 BMenu::_QuitTracking(bool onlyThis)
3003 _SelectItem(NULL);
3004 if (BMenuBar* menuBar = dynamic_cast<BMenuBar*>(this))
3005 menuBar->_RestoreFocus();
3007 fState = MENU_STATE_CLOSED;
3009 if (!onlyThis) {
3010 // Close the whole menu hierarchy
3011 if (Supermenu() != NULL)
3012 Supermenu()->fState = MENU_STATE_CLOSED;
3014 if (_IsStickyMode())
3015 _SetStickyMode(false);
3017 if (LockLooper()) {
3018 be_app->ShowCursor();
3019 UnlockLooper();
3023 _Hide();
3027 // #pragma mark - menu_info functions
3030 // TODO: Maybe the following two methods would fit better into
3031 // InterfaceDefs.cpp
3032 // In R5, they do all the work client side, we let the app_server handle the
3033 // details.
3034 status_t
3035 set_menu_info(menu_info* info)
3037 if (!info)
3038 return B_BAD_VALUE;
3040 BPrivate::AppServerLink link;
3041 link.StartMessage(AS_SET_MENU_INFO);
3042 link.Attach<menu_info>(*info);
3044 status_t status = B_ERROR;
3045 if (link.FlushWithReply(status) == B_OK && status == B_OK)
3046 BMenu::sMenuInfo = *info;
3047 // Update also the local copy, in case anyone relies on it
3049 return status;
3053 status_t
3054 get_menu_info(menu_info* info)
3056 if (!info)
3057 return B_BAD_VALUE;
3059 BPrivate::AppServerLink link;
3060 link.StartMessage(AS_GET_MENU_INFO);
3062 status_t status = B_ERROR;
3063 if (link.FlushWithReply(status) == B_OK && status == B_OK)
3064 link.Read<menu_info>(info);
3066 return status;
3070 extern "C" void
3071 B_IF_GCC_2(InvalidateLayout__5BMenub,_ZN5BMenu16InvalidateLayoutEb)(
3072 BMenu* menu, bool descendants)
3074 menu->InvalidateLayout();