tcp: Fix 64 bit build with debugging features enabled.
[haiku.git] / src / kits / interface / Menu.cpp
blob58aaeb8c151ca6ad9e9c308056a1cd8aa5faf978
1 /*
2 * Copyright 2001-2013 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."
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(14.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 fSelected(NULL),
255 fCachedMenuWindow(NULL),
256 fSuper(NULL),
257 fSuperitem(NULL),
258 fAscent(-1.0f),
259 fDescent(-1.0f),
260 fFontHeight(-1.0f),
261 fState(0),
262 fLayout(B_ITEMS_IN_MATRIX),
263 fExtraRect(NULL),
264 fMaxContentWidth(0.0f),
265 fInitMatrixSize(NULL),
266 fExtraMenuData(NULL),
267 fTrigger(0),
268 fResizeToFit(true),
269 fUseCachedMenuLayout(false),
270 fEnabled(true),
271 fDynamicName(false),
272 fRadioMode(false),
273 fTrackNewBounds(false),
274 fStickyMode(false),
275 fIgnoreHidden(true),
276 fTriggerEnabled(true),
277 fRedrawAfterSticky(false),
278 fAttachAborted(false)
280 _InitData(NULL);
284 BMenu::BMenu(BMessage* archive)
286 BView(archive),
287 fChosenItem(NULL),
288 fPad(14.0f, 2.0f, 20.0f, 0.0f),
289 fSelected(NULL),
290 fCachedMenuWindow(NULL),
291 fSuper(NULL),
292 fSuperitem(NULL),
293 fAscent(-1.0f),
294 fDescent(-1.0f),
295 fFontHeight(-1.0f),
296 fState(MENU_STATE_CLOSED),
297 fLayout(B_ITEMS_IN_ROW),
298 fExtraRect(NULL),
299 fMaxContentWidth(0.0f),
300 fInitMatrixSize(NULL),
301 fExtraMenuData(NULL),
302 fTrigger(0),
303 fResizeToFit(true),
304 fUseCachedMenuLayout(false),
305 fEnabled(true),
306 fDynamicName(false),
307 fRadioMode(false),
308 fTrackNewBounds(false),
309 fStickyMode(false),
310 fIgnoreHidden(true),
311 fTriggerEnabled(true),
312 fRedrawAfterSticky(false),
313 fAttachAborted(false)
315 _InitData(archive);
319 BMenu::~BMenu()
321 _DeleteMenuWindow();
323 RemoveItems(0, CountItems(), true);
325 delete fInitMatrixSize;
326 delete fExtraMenuData;
327 delete fLayoutData;
331 BArchivable*
332 BMenu::Instantiate(BMessage* archive)
334 if (validate_instantiation(archive, "BMenu"))
335 return new (nothrow) BMenu(archive);
337 return NULL;
341 status_t
342 BMenu::Archive(BMessage* data, bool deep) const
344 status_t err = BView::Archive(data, deep);
346 if (err == B_OK && Layout() != B_ITEMS_IN_ROW)
347 err = data->AddInt32("_layout", Layout());
348 if (err == B_OK)
349 err = data->AddBool("_rsize_to_fit", fResizeToFit);
350 if (err == B_OK)
351 err = data->AddBool("_disable", !IsEnabled());
352 if (err == B_OK)
353 err = data->AddBool("_radio", IsRadioMode());
354 if (err == B_OK)
355 err = data->AddBool("_trig_disabled", AreTriggersEnabled());
356 if (err == B_OK)
357 err = data->AddBool("_dyn_label", fDynamicName);
358 if (err == B_OK)
359 err = data->AddFloat("_maxwidth", fMaxContentWidth);
360 if (err == B_OK && deep) {
361 BMenuItem* item = NULL;
362 int32 index = 0;
363 while ((item = ItemAt(index++)) != NULL) {
364 BMessage itemData;
365 item->Archive(&itemData, deep);
366 err = data->AddMessage("_items", &itemData);
367 if (err != B_OK)
368 break;
369 if (fLayout == B_ITEMS_IN_MATRIX) {
370 err = data->AddRect("_i_frames", item->fBounds);
375 return err;
379 void
380 BMenu::AttachedToWindow()
382 BView::AttachedToWindow();
384 _GetShiftKey(sShiftKey);
385 _GetControlKey(sControlKey);
386 _GetCommandKey(sCommandKey);
387 _GetOptionKey(sOptionKey);
388 _GetMenuKey(sMenuKey);
390 fAttachAborted = _AddDynamicItems();
392 if (!fAttachAborted) {
393 _CacheFontInfo();
394 _LayoutItems(0);
395 _UpdateWindowViewSize(false);
400 void
401 BMenu::DetachedFromWindow()
403 BView::DetachedFromWindow();
407 void
408 BMenu::AllAttached()
410 BView::AllAttached();
414 void
415 BMenu::AllDetached()
417 BView::AllDetached();
421 void
422 BMenu::Draw(BRect updateRect)
424 if (_RelayoutIfNeeded()) {
425 Invalidate();
426 return;
429 DrawBackground(updateRect);
430 _DrawItems(updateRect);
434 void
435 BMenu::MessageReceived(BMessage* message)
437 switch (message->what) {
438 case B_MOUSE_WHEEL_CHANGED:
440 float deltaY = 0;
441 message->FindFloat("be:wheel_delta_y", &deltaY);
442 if (deltaY == 0)
443 return;
445 BMenuWindow* window = dynamic_cast<BMenuWindow*>(Window());
446 if (window == NULL)
447 return;
449 float largeStep;
450 float smallStep;
451 window->GetSteps(&smallStep, &largeStep);
453 // pressing the shift key scrolls faster
454 if ((modifiers() & B_SHIFT_KEY) != 0)
455 deltaY *= largeStep;
456 else
457 deltaY *= smallStep;
459 window->TryScrollBy(deltaY);
460 break;
463 default:
464 BView::MessageReceived(message);
465 break;
470 void
471 BMenu::KeyDown(const char* bytes, int32 numBytes)
473 // TODO: Test how it works on BeOS R5 and implement this correctly
474 switch (bytes[0]) {
475 case B_UP_ARROW:
476 if (fLayout == B_ITEMS_IN_COLUMN)
477 _SelectNextItem(fSelected, false);
478 break;
480 case B_DOWN_ARROW:
482 BMenuBar* bar = dynamic_cast<BMenuBar*>(Supermenu());
483 if (bar != NULL && fState == MENU_STATE_CLOSED) {
484 // tell MenuBar's _Track:
485 bar->fState = MENU_STATE_KEY_TO_SUBMENU;
487 if (fLayout == B_ITEMS_IN_COLUMN)
488 _SelectNextItem(fSelected, true);
489 break;
492 case B_LEFT_ARROW:
493 if (fLayout == B_ITEMS_IN_ROW)
494 _SelectNextItem(fSelected, false);
495 else {
496 // this case has to be handled a bit specially.
497 BMenuItem* item = Superitem();
498 if (item) {
499 if (dynamic_cast<BMenuBar*>(Supermenu())) {
500 // If we're at the top menu below the menu bar, pass
501 // the keypress to the menu bar so we can move to
502 // another top level menu.
503 BMessenger msgr(Supermenu());
504 msgr.SendMessage(Window()->CurrentMessage());
505 } else {
506 // tell _Track
507 fState = MENU_STATE_KEY_LEAVE_SUBMENU;
511 break;
513 case B_RIGHT_ARROW:
514 if (fLayout == B_ITEMS_IN_ROW)
515 _SelectNextItem(fSelected, true);
516 else {
517 if (fSelected != NULL && fSelected->Submenu() != NULL) {
518 fSelected->Submenu()->_SetStickyMode(true);
519 // fix me: this shouldn't be needed but dynamic menus
520 // aren't getting it set correctly when keyboard
521 // navigating, which aborts the attach
522 fState = MENU_STATE_KEY_TO_SUBMENU;
523 _SelectItem(fSelected, true, true, true);
524 } else if (dynamic_cast<BMenuBar*>(Supermenu())) {
525 // if we have no submenu and we're an
526 // item in the top menu below the menubar,
527 // pass the keypress to the menubar
528 // so you can use the keypress to switch menus.
529 BMessenger msgr(Supermenu());
530 msgr.SendMessage(Window()->CurrentMessage());
533 break;
535 case B_PAGE_UP:
536 case B_PAGE_DOWN:
538 BMenuWindow* window = dynamic_cast<BMenuWindow*>(Window());
539 if (window == NULL || !window->HasScrollers())
540 break;
542 int32 deltaY = bytes[0] == B_PAGE_UP ? -1 : 1;
544 float largeStep;
545 window->GetSteps(NULL, &largeStep);
546 window->TryScrollBy(deltaY * largeStep);
547 break;
550 case B_ENTER:
551 case B_SPACE:
552 if (fSelected != NULL) {
553 fChosenItem = fSelected;
554 // preserve for exit handling
555 _QuitTracking(false);
557 break;
559 case B_ESCAPE:
560 _SelectItem(NULL);
561 if (fState == MENU_STATE_CLOSED
562 && dynamic_cast<BMenuBar*>(Supermenu())) {
563 // Keyboard may show menu without tracking it
564 BMessenger messenger(Supermenu());
565 messenger.SendMessage(Window()->CurrentMessage());
566 } else
567 _QuitTracking(false);
568 break;
570 default:
572 uint32 trigger = UTF8ToCharCode(&bytes);
574 for (uint32 i = CountItems(); i-- > 0;) {
575 BMenuItem* item = ItemAt(i);
576 if (item->fTriggerIndex < 0 || item->fTrigger != trigger)
577 continue;
579 _InvokeItem(item);
580 _QuitTracking(false);
581 break;
583 break;
589 BSize
590 BMenu::MinSize()
592 _ValidatePreferredSize();
594 BSize size = (GetLayout() != NULL ? GetLayout()->MinSize()
595 : fLayoutData->preferred);
597 return BLayoutUtils::ComposeSize(ExplicitMinSize(), size);
601 BSize
602 BMenu::MaxSize()
604 _ValidatePreferredSize();
606 BSize size = (GetLayout() != NULL ? GetLayout()->MaxSize()
607 : fLayoutData->preferred);
609 return BLayoutUtils::ComposeSize(ExplicitMaxSize(), size);
613 BSize
614 BMenu::PreferredSize()
616 _ValidatePreferredSize();
618 BSize size = (GetLayout() != NULL ? GetLayout()->PreferredSize()
619 : fLayoutData->preferred);
621 return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), size);
625 void
626 BMenu::GetPreferredSize(float* _width, float* _height)
628 _ValidatePreferredSize();
630 if (_width)
631 *_width = fLayoutData->preferred.width;
633 if (_height)
634 *_height = fLayoutData->preferred.height;
638 void
639 BMenu::ResizeToPreferred()
641 BView::ResizeToPreferred();
645 void
646 BMenu::DoLayout()
648 // If the user set a layout, we let the base class version call its
649 // hook.
650 if (GetLayout() != NULL) {
651 BView::DoLayout();
652 return;
655 if (_RelayoutIfNeeded())
656 Invalidate();
660 void
661 BMenu::FrameMoved(BPoint new_position)
663 BView::FrameMoved(new_position);
667 void
668 BMenu::FrameResized(float new_width, float new_height)
670 BView::FrameResized(new_width, new_height);
674 void
675 BMenu::InvalidateLayout()
677 fUseCachedMenuLayout = false;
678 // This method exits for backwards compatibility reasons, it is used to
679 // invalidate the menu layout, but we also use call
680 // BView::InvalidateLayout() for good measure. Don't delete this method!
681 BView::InvalidateLayout(false);
685 void
686 BMenu::MakeFocus(bool focused)
688 BView::MakeFocus(focused);
692 bool
693 BMenu::AddItem(BMenuItem* item)
695 return AddItem(item, CountItems());
699 bool
700 BMenu::AddItem(BMenuItem* item, int32 index)
702 if (fLayout == B_ITEMS_IN_MATRIX) {
703 debugger("BMenu::AddItem(BMenuItem*, int32) this method can only "
704 "be called if the menu layout is not B_ITEMS_IN_MATRIX");
707 if (!item || !_AddItem(item, index))
708 return false;
710 InvalidateLayout();
711 if (LockLooper()) {
712 if (!Window()->IsHidden()) {
713 _LayoutItems(index);
714 _UpdateWindowViewSize(false);
715 Invalidate();
717 UnlockLooper();
720 return true;
724 bool
725 BMenu::AddItem(BMenuItem* item, BRect frame)
727 if (fLayout != B_ITEMS_IN_MATRIX) {
728 debugger("BMenu::AddItem(BMenuItem*, BRect) this method can only "
729 "be called if the menu layout is B_ITEMS_IN_MATRIX");
732 if (item == NULL)
733 return false;
735 item->fBounds = frame;
737 int32 index = CountItems();
738 if (!_AddItem(item, index))
739 return false;
741 if (LockLooper()) {
742 if (!Window()->IsHidden()) {
743 _LayoutItems(index);
744 Invalidate();
746 UnlockLooper();
749 return true;
753 bool
754 BMenu::AddItem(BMenu* submenu)
756 BMenuItem* item = new (nothrow) BMenuItem(submenu);
757 if (item == NULL)
758 return false;
760 if (!AddItem(item, CountItems())) {
761 item->fSubmenu = NULL;
762 delete item;
763 return false;
766 return true;
770 bool
771 BMenu::AddItem(BMenu* submenu, int32 index)
773 if (fLayout == B_ITEMS_IN_MATRIX) {
774 debugger("BMenu::AddItem(BMenuItem*, int32) this method can only "
775 "be called if the menu layout is not B_ITEMS_IN_MATRIX");
778 BMenuItem* item = new (nothrow) BMenuItem(submenu);
779 if (item == NULL)
780 return false;
782 if (!AddItem(item, index)) {
783 item->fSubmenu = NULL;
784 delete item;
785 return false;
788 return true;
792 bool
793 BMenu::AddItem(BMenu* submenu, BRect frame)
795 if (fLayout != B_ITEMS_IN_MATRIX) {
796 debugger("BMenu::AddItem(BMenu*, BRect) this method can only "
797 "be called if the menu layout is B_ITEMS_IN_MATRIX");
800 BMenuItem* item = new (nothrow) BMenuItem(submenu);
801 if (item == NULL)
802 return false;
804 if (!AddItem(item, frame)) {
805 item->fSubmenu = NULL;
806 delete item;
807 return false;
810 return true;
814 bool
815 BMenu::AddList(BList* list, int32 index)
817 // TODO: test this function, it's not documented in the bebook.
818 if (list == NULL)
819 return false;
821 bool locked = LockLooper();
823 int32 numItems = list->CountItems();
824 for (int32 i = 0; i < numItems; i++) {
825 BMenuItem* item = static_cast<BMenuItem*>(list->ItemAt(i));
826 if (item != NULL) {
827 if (!_AddItem(item, index + i))
828 break;
832 InvalidateLayout();
833 if (locked && Window() != NULL && !Window()->IsHidden()) {
834 // Make sure we update the layout if needed.
835 _LayoutItems(index);
836 _UpdateWindowViewSize(false);
837 Invalidate();
840 if (locked)
841 UnlockLooper();
843 return true;
847 bool
848 BMenu::AddSeparatorItem()
850 BMenuItem* item = new (nothrow) BSeparatorItem();
851 if (!item || !AddItem(item, CountItems())) {
852 delete item;
853 return false;
856 return true;
860 bool
861 BMenu::RemoveItem(BMenuItem* item)
863 return _RemoveItems(0, 0, item, false);
867 BMenuItem*
868 BMenu::RemoveItem(int32 index)
870 BMenuItem* item = ItemAt(index);
871 if (item != NULL)
872 _RemoveItems(0, 0, item, false);
873 return item;
877 bool
878 BMenu::RemoveItems(int32 index, int32 count, bool deleteItems)
880 return _RemoveItems(index, count, NULL, deleteItems);
884 bool
885 BMenu::RemoveItem(BMenu* submenu)
887 for (int32 i = 0; i < fItems.CountItems(); i++) {
888 if (static_cast<BMenuItem*>(fItems.ItemAtFast(i))->Submenu()
889 == submenu) {
890 return _RemoveItems(i, 1, NULL, false);
894 return false;
898 int32
899 BMenu::CountItems() const
901 return fItems.CountItems();
905 BMenuItem*
906 BMenu::ItemAt(int32 index) const
908 return static_cast<BMenuItem*>(fItems.ItemAt(index));
912 BMenu*
913 BMenu::SubmenuAt(int32 index) const
915 BMenuItem* item = static_cast<BMenuItem*>(fItems.ItemAt(index));
916 return item != NULL ? item->Submenu() : NULL;
920 int32
921 BMenu::IndexOf(BMenuItem* item) const
923 return fItems.IndexOf(item);
927 int32
928 BMenu::IndexOf(BMenu* submenu) const
930 for (int32 i = 0; i < fItems.CountItems(); i++) {
931 if (ItemAt(i)->Submenu() == submenu)
932 return i;
935 return -1;
939 BMenuItem*
940 BMenu::FindItem(const char* label) const
942 BMenuItem* item = NULL;
944 for (int32 i = 0; i < CountItems(); i++) {
945 item = ItemAt(i);
947 if (item->Label() && strcmp(item->Label(), label) == 0)
948 return item;
950 if (item->Submenu() != NULL) {
951 item = item->Submenu()->FindItem(label);
952 if (item != NULL)
953 return item;
957 return NULL;
961 BMenuItem*
962 BMenu::FindItem(uint32 command) const
964 BMenuItem* item = NULL;
966 for (int32 i = 0; i < CountItems(); i++) {
967 item = ItemAt(i);
969 if (item->Command() == command)
970 return item;
972 if (item->Submenu() != NULL) {
973 item = item->Submenu()->FindItem(command);
974 if (item != NULL)
975 return item;
979 return NULL;
983 status_t
984 BMenu::SetTargetForItems(BHandler* handler)
986 status_t status = B_OK;
987 for (int32 i = 0; i < fItems.CountItems(); i++) {
988 status = ItemAt(i)->SetTarget(handler);
989 if (status < B_OK)
990 break;
993 return status;
997 status_t
998 BMenu::SetTargetForItems(BMessenger messenger)
1000 status_t status = B_OK;
1001 for (int32 i = 0; i < fItems.CountItems(); i++) {
1002 status = ItemAt(i)->SetTarget(messenger);
1003 if (status < B_OK)
1004 break;
1007 return status;
1011 void
1012 BMenu::SetEnabled(bool enable)
1014 if (fEnabled == enable)
1015 return;
1017 fEnabled = enable;
1019 if (dynamic_cast<_BMCMenuBar_*>(Supermenu()) != NULL)
1020 Supermenu()->SetEnabled(enable);
1022 if (fSuperitem)
1023 fSuperitem->SetEnabled(enable);
1027 void
1028 BMenu::SetRadioMode(bool on)
1030 fRadioMode = on;
1031 if (!on)
1032 SetLabelFromMarked(false);
1036 void
1037 BMenu::SetTriggersEnabled(bool enable)
1039 fTriggerEnabled = enable;
1043 void
1044 BMenu::SetMaxContentWidth(float width)
1046 fMaxContentWidth = width;
1050 void
1051 BMenu::SetLabelFromMarked(bool on)
1053 fDynamicName = on;
1054 if (on)
1055 SetRadioMode(true);
1059 bool
1060 BMenu::IsLabelFromMarked()
1062 return fDynamicName;
1066 bool
1067 BMenu::IsEnabled() const
1069 if (!fEnabled)
1070 return false;
1072 return fSuper ? fSuper->IsEnabled() : true ;
1076 bool
1077 BMenu::IsRadioMode() const
1079 return fRadioMode;
1083 bool
1084 BMenu::AreTriggersEnabled() const
1086 return fTriggerEnabled;
1090 bool
1091 BMenu::IsRedrawAfterSticky() const
1093 return fRedrawAfterSticky;
1097 float
1098 BMenu::MaxContentWidth() const
1100 return fMaxContentWidth;
1104 BMenuItem*
1105 BMenu::FindMarked()
1107 for (int32 i = 0; i < fItems.CountItems(); i++) {
1108 BMenuItem* item = ItemAt(i);
1110 if (item->IsMarked())
1111 return item;
1114 return NULL;
1118 int32
1119 BMenu::FindMarkedIndex()
1121 for (int32 i = 0; i < fItems.CountItems(); i++) {
1122 BMenuItem* item = ItemAt(i);
1124 if (item->IsMarked())
1125 return i;
1128 return -1;
1132 BMenu*
1133 BMenu::Supermenu() const
1135 return fSuper;
1139 BMenuItem*
1140 BMenu::Superitem() const
1142 return fSuperitem;
1146 BHandler*
1147 BMenu::ResolveSpecifier(BMessage* msg, int32 index, BMessage* specifier,
1148 int32 form, const char* property)
1150 BPropertyInfo propInfo(sPropList);
1151 BHandler* target = NULL;
1153 switch (propInfo.FindMatch(msg, 0, specifier, form, property)) {
1154 case B_ERROR:
1155 break;
1157 case 0:
1158 case 1:
1159 case 2:
1160 case 3:
1161 case 4:
1162 case 5:
1163 case 6:
1164 case 7:
1165 target = this;
1166 break;
1167 case 8:
1168 // TODO: redirect to menu
1169 target = this;
1170 break;
1171 case 9:
1172 case 10:
1173 case 11:
1174 case 12:
1175 target = this;
1176 break;
1177 case 13:
1178 // TODO: redirect to menuitem
1179 target = this;
1180 break;
1183 if (!target)
1184 target = BView::ResolveSpecifier(msg, index, specifier, form,
1185 property);
1187 return target;
1191 status_t
1192 BMenu::GetSupportedSuites(BMessage* data)
1194 if (data == NULL)
1195 return B_BAD_VALUE;
1197 status_t err = data->AddString("suites", "suite/vnd.Be-menu");
1199 if (err < B_OK)
1200 return err;
1202 BPropertyInfo propertyInfo(sPropList);
1203 err = data->AddFlat("messages", &propertyInfo);
1205 if (err < B_OK)
1206 return err;
1208 return BView::GetSupportedSuites(data);
1212 status_t
1213 BMenu::Perform(perform_code code, void* _data)
1215 switch (code) {
1216 case PERFORM_CODE_MIN_SIZE:
1217 ((perform_data_min_size*)_data)->return_value
1218 = BMenu::MinSize();
1219 return B_OK;
1221 case PERFORM_CODE_MAX_SIZE:
1222 ((perform_data_max_size*)_data)->return_value
1223 = BMenu::MaxSize();
1224 return B_OK;
1226 case PERFORM_CODE_PREFERRED_SIZE:
1227 ((perform_data_preferred_size*)_data)->return_value
1228 = BMenu::PreferredSize();
1229 return B_OK;
1231 case PERFORM_CODE_LAYOUT_ALIGNMENT:
1232 ((perform_data_layout_alignment*)_data)->return_value
1233 = BMenu::LayoutAlignment();
1234 return B_OK;
1236 case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH:
1237 ((perform_data_has_height_for_width*)_data)->return_value
1238 = BMenu::HasHeightForWidth();
1239 return B_OK;
1241 case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH:
1243 perform_data_get_height_for_width* data
1244 = (perform_data_get_height_for_width*)_data;
1245 BMenu::GetHeightForWidth(data->width, &data->min, &data->max,
1246 &data->preferred);
1247 return B_OK;
1250 case PERFORM_CODE_SET_LAYOUT:
1252 perform_data_set_layout* data = (perform_data_set_layout*)_data;
1253 BMenu::SetLayout(data->layout);
1254 return B_OK;
1257 case PERFORM_CODE_LAYOUT_INVALIDATED:
1259 perform_data_layout_invalidated* data
1260 = (perform_data_layout_invalidated*)_data;
1261 BMenu::LayoutInvalidated(data->descendants);
1262 return B_OK;
1265 case PERFORM_CODE_DO_LAYOUT:
1267 BMenu::DoLayout();
1268 return B_OK;
1272 return BView::Perform(code, _data);
1276 // #pragma mark - BMenu protected methods
1279 BMenu::BMenu(BRect frame, const char* name, uint32 resizingMode, uint32 flags,
1280 menu_layout layout, bool resizeToFit)
1282 BView(frame, name, resizingMode, flags),
1283 fChosenItem(NULL),
1284 fSelected(NULL),
1285 fCachedMenuWindow(NULL),
1286 fSuper(NULL),
1287 fSuperitem(NULL),
1288 fAscent(-1.0f),
1289 fDescent(-1.0f),
1290 fFontHeight(-1.0f),
1291 fState(MENU_STATE_CLOSED),
1292 fLayout(layout),
1293 fExtraRect(NULL),
1294 fMaxContentWidth(0.0f),
1295 fInitMatrixSize(NULL),
1296 fExtraMenuData(NULL),
1297 fTrigger(0),
1298 fResizeToFit(resizeToFit),
1299 fUseCachedMenuLayout(false),
1300 fEnabled(true),
1301 fDynamicName(false),
1302 fRadioMode(false),
1303 fTrackNewBounds(false),
1304 fStickyMode(false),
1305 fIgnoreHidden(true),
1306 fTriggerEnabled(true),
1307 fRedrawAfterSticky(false),
1308 fAttachAborted(false)
1310 _InitData(NULL);
1314 void
1315 BMenu::SetItemMargins(float left, float top, float right, float bottom)
1317 fPad.Set(left, top, right, bottom);
1321 void
1322 BMenu::GetItemMargins(float* _left, float* _top, float* _right,
1323 float* _bottom) const
1325 if (_left != NULL)
1326 *_left = fPad.left;
1328 if (_top != NULL)
1329 *_top = fPad.top;
1331 if (_right != NULL)
1332 *_right = fPad.right;
1334 if (_bottom != NULL)
1335 *_bottom = fPad.bottom;
1339 menu_layout
1340 BMenu::Layout() const
1342 return fLayout;
1346 void
1347 BMenu::Show()
1349 Show(false);
1353 void
1354 BMenu::Show(bool selectFirst)
1356 _Install(NULL);
1357 _Show(selectFirst);
1361 void
1362 BMenu::Hide()
1364 _Hide();
1365 _Uninstall();
1369 BMenuItem*
1370 BMenu::Track(bool sticky, BRect* clickToOpenRect)
1372 if (sticky && LockLooper()) {
1373 //RedrawAfterSticky(Bounds());
1374 // the call above didn't do anything, so I've removed it for now
1375 UnlockLooper();
1378 if (clickToOpenRect != NULL && LockLooper()) {
1379 fExtraRect = clickToOpenRect;
1380 ConvertFromScreen(fExtraRect);
1381 UnlockLooper();
1384 _SetStickyMode(sticky);
1386 int action;
1387 BMenuItem* menuItem = _Track(&action);
1389 fExtraRect = NULL;
1391 return menuItem;
1395 // #pragma mark - BMenu private methods
1398 bool
1399 BMenu::AddDynamicItem(add_state state)
1401 // Implemented in subclasses
1402 return false;
1406 void
1407 BMenu::DrawBackground(BRect updateRect)
1409 if (be_control_look != NULL) {
1410 rgb_color base = sMenuInfo.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);
1434 return;
1437 rgb_color oldColor = HighColor();
1438 SetHighColor(sMenuInfo.background_color);
1439 FillRect(Bounds() & updateRect, B_SOLID_HIGH);
1440 SetHighColor(oldColor);
1444 void
1445 BMenu::SetTrackingHook(menu_tracking_hook func, void* state)
1447 delete fExtraMenuData;
1448 fExtraMenuData = new (nothrow) BPrivate::ExtraMenuData(func, state);
1452 void BMenu::_ReservedMenu3() {}
1453 void BMenu::_ReservedMenu4() {}
1454 void BMenu::_ReservedMenu5() {}
1455 void BMenu::_ReservedMenu6() {}
1458 void
1459 BMenu::_InitData(BMessage* archive)
1461 BPrivate::kEmptyMenuLabel = B_TRANSLATE("<empty>");
1463 // TODO: Get _color, _fname, _fflt from the message, if present
1464 BFont font;
1465 font.SetFamilyAndStyle(sMenuInfo.f_family, sMenuInfo.f_style);
1466 font.SetSize(sMenuInfo.font_size);
1467 SetFont(&font, B_FONT_FAMILY_AND_STYLE | B_FONT_SIZE);
1469 fLayoutData = new LayoutData;
1470 fLayoutData->lastResizingMode = ResizingMode();
1472 SetLowColor(sMenuInfo.background_color);
1473 SetViewColor(B_TRANSPARENT_COLOR);
1475 fTriggerEnabled = sMenuInfo.triggers_always_shown;
1477 if (archive != NULL) {
1478 archive->FindInt32("_layout", (int32*)&fLayout);
1479 archive->FindBool("_rsize_to_fit", &fResizeToFit);
1480 bool disabled;
1481 if (archive->FindBool("_disable", &disabled) == B_OK)
1482 fEnabled = !disabled;
1483 archive->FindBool("_radio", &fRadioMode);
1485 bool disableTrigger = false;
1486 archive->FindBool("_trig_disabled", &disableTrigger);
1487 fTriggerEnabled = !disableTrigger;
1489 archive->FindBool("_dyn_label", &fDynamicName);
1490 archive->FindFloat("_maxwidth", &fMaxContentWidth);
1492 BMessage msg;
1493 for (int32 i = 0; archive->FindMessage("_items", i, &msg) == B_OK; i++) {
1494 BArchivable* object = instantiate_object(&msg);
1495 if (BMenuItem* item = dynamic_cast<BMenuItem*>(object)) {
1496 BRect bounds;
1497 if (fLayout == B_ITEMS_IN_MATRIX
1498 && archive->FindRect("_i_frames", i, &bounds) == B_OK)
1499 AddItem(item, bounds);
1500 else
1501 AddItem(item);
1508 bool
1509 BMenu::_Show(bool selectFirstItem, bool keyDown)
1511 if (Window() != NULL)
1512 return false;
1514 // See if the supermenu has a cached menuwindow,
1515 // and use that one if possible.
1516 BMenuWindow* window = NULL;
1517 bool ourWindow = false;
1518 if (fSuper != NULL) {
1519 fSuperbounds = fSuper->ConvertToScreen(fSuper->Bounds());
1520 window = fSuper->_MenuWindow();
1523 // Otherwise, create a new one
1524 // This happens for "stand alone" BPopUpMenus
1525 // (i.e. not within a BMenuField)
1526 if (window == NULL) {
1527 // Menu windows get the BMenu's handler name
1528 window = new (nothrow) BMenuWindow(Name());
1529 ourWindow = true;
1532 if (window == NULL)
1533 return false;
1535 if (window->Lock()) {
1536 bool addAborted = false;
1537 if (keyDown)
1538 addAborted = _AddDynamicItems(keyDown);
1540 if (addAborted) {
1541 if (ourWindow)
1542 window->Quit();
1543 else
1544 window->Unlock();
1545 return false;
1547 fAttachAborted = false;
1549 window->AttachMenu(this);
1551 if (ItemAt(0) != NULL) {
1552 float width, height;
1553 ItemAt(0)->GetContentSize(&width, &height);
1555 window->SetSmallStep(ceilf(height));
1558 // Menu didn't have the time to add its items: aborting...
1559 if (fAttachAborted) {
1560 window->DetachMenu();
1561 // TODO: Probably not needed, we can just let _hide() quit the
1562 // window.
1563 if (ourWindow)
1564 window->Quit();
1565 else
1566 window->Unlock();
1567 return false;
1570 _UpdateWindowViewSize(true);
1571 window->Show();
1573 if (selectFirstItem)
1574 _SelectItem(ItemAt(0), false);
1576 window->Unlock();
1579 return true;
1583 void
1584 BMenu::_Hide()
1586 BMenuWindow* window = dynamic_cast<BMenuWindow*>(Window());
1587 if (window == NULL || !window->Lock())
1588 return;
1590 if (fSelected != NULL)
1591 _SelectItem(NULL);
1593 window->Hide();
1594 window->DetachMenu();
1595 // we don't want to be deleted when the window is removed
1597 #if USE_CACHED_MENUWINDOW
1598 if (fSuper != NULL)
1599 window->Unlock();
1600 else
1601 #endif
1602 window->Quit();
1603 // it's our window, quit it
1605 _DeleteMenuWindow();
1606 // Delete the menu window used by our submenus
1610 // #pragma mark - mouse tracking
1613 const static bigtime_t kOpenSubmenuDelay = 225000;
1614 const static bigtime_t kNavigationAreaTimeout = 1000000;
1617 BMenuItem*
1618 BMenu::_Track(int* action, long start)
1620 // TODO: cleanup
1621 BMenuItem* item = NULL;
1622 BRect navAreaRectAbove;
1623 BRect navAreaRectBelow;
1624 bigtime_t selectedTime = system_time();
1625 bigtime_t navigationAreaTime = 0;
1627 fState = MENU_STATE_TRACKING;
1628 fChosenItem = NULL;
1629 // we will use this for keyboard selection
1631 BPoint location;
1632 uint32 buttons = 0;
1633 if (LockLooper()) {
1634 GetMouse(&location, &buttons);
1635 UnlockLooper();
1638 bool releasedOnce = buttons == 0;
1639 while (fState != MENU_STATE_CLOSED) {
1640 if (_CustomTrackingWantsToQuit())
1641 break;
1643 if (!LockLooper())
1644 break;
1646 BMenuWindow* window = static_cast<BMenuWindow*>(Window());
1647 BPoint screenLocation = ConvertToScreen(location);
1648 if (window->CheckForScrolling(screenLocation)) {
1649 UnlockLooper();
1650 continue;
1653 // The order of the checks is important
1654 // to be able to handle overlapping menus:
1655 // first we check if mouse is inside a submenu,
1656 // then if the mouse is inside this menu,
1657 // then if it's over a super menu.
1658 if (_OverSubmenu(fSelected, screenLocation)
1659 || fState == MENU_STATE_KEY_TO_SUBMENU) {
1660 if (fState == MENU_STATE_TRACKING) {
1661 // not if from R.Arrow
1662 fState = MENU_STATE_TRACKING_SUBMENU;
1664 navAreaRectAbove = BRect();
1665 navAreaRectBelow = BRect();
1667 // Since the submenu has its own looper,
1668 // we can unlock ours. Doing so also make sure
1669 // that our window gets any update message to
1670 // redraw itself
1671 UnlockLooper();
1672 int submenuAction = MENU_STATE_TRACKING;
1673 BMenu* submenu = fSelected->Submenu();
1674 submenu->_SetStickyMode(_IsStickyMode());
1676 // The following call blocks until the submenu
1677 // gives control back to us, either because the mouse
1678 // pointer goes out of the submenu's bounds, or because
1679 // the user closes the menu
1680 BMenuItem* submenuItem = submenu->_Track(&submenuAction);
1681 if (submenuAction == MENU_STATE_CLOSED) {
1682 item = submenuItem;
1683 fState = MENU_STATE_CLOSED;
1684 } else if (submenuAction == MENU_STATE_KEY_LEAVE_SUBMENU) {
1685 if (LockLooper()) {
1686 BMenuItem* temp = fSelected;
1687 // close the submenu:
1688 _SelectItem(NULL);
1689 // but reselect the item itself for user:
1690 _SelectItem(temp, false);
1691 UnlockLooper();
1693 // cancel key-nav state
1694 fState = MENU_STATE_TRACKING;
1695 } else
1696 fState = MENU_STATE_TRACKING;
1697 if (!LockLooper())
1698 break;
1699 } else if ((item = _HitTestItems(location, B_ORIGIN)) != NULL) {
1700 _UpdateStateOpenSelect(item, location, navAreaRectAbove,
1701 navAreaRectBelow, selectedTime, navigationAreaTime);
1702 releasedOnce = true;
1703 } else if (_OverSuper(screenLocation)
1704 && fSuper->fState != MENU_STATE_KEY_TO_SUBMENU) {
1705 fState = MENU_STATE_TRACKING;
1706 UnlockLooper();
1707 break;
1708 } else if (fState == MENU_STATE_KEY_LEAVE_SUBMENU) {
1709 UnlockLooper();
1710 break;
1711 } else if (fSuper == NULL
1712 || fSuper->fState != MENU_STATE_KEY_TO_SUBMENU) {
1713 // Mouse pointer outside menu:
1714 // If there's no other submenu opened,
1715 // deselect the current selected item
1716 if (fSelected != NULL
1717 && (fSelected->Submenu() == NULL
1718 || fSelected->Submenu()->Window() == NULL)) {
1719 _SelectItem(NULL);
1720 fState = MENU_STATE_TRACKING;
1723 if (fSuper != NULL) {
1724 // Give supermenu the chance to continue tracking
1725 *action = fState;
1726 UnlockLooper();
1727 return NULL;
1731 UnlockLooper();
1733 if (releasedOnce)
1734 _UpdateStateClose(item, location, buttons);
1736 if (fState != MENU_STATE_CLOSED) {
1737 bigtime_t snoozeAmount = 50000;
1739 BPoint newLocation = location;
1740 uint32 newButtons = buttons;
1742 // If user doesn't move the mouse, loop here,
1743 // so we don't interfere with keyboard menu navigation
1744 do {
1745 snooze(snoozeAmount);
1746 if (!LockLooper())
1747 break;
1748 GetMouse(&newLocation, &newButtons, true);
1749 UnlockLooper();
1750 } while (newLocation == location && newButtons == buttons
1751 && !(item != NULL && item->Submenu() != NULL
1752 && item->Submenu()->Window() == NULL)
1753 && fState == MENU_STATE_TRACKING);
1755 if (newLocation != location || newButtons != buttons) {
1756 if (!releasedOnce && newButtons == 0 && buttons != 0)
1757 releasedOnce = true;
1758 location = newLocation;
1759 buttons = newButtons;
1762 if (releasedOnce)
1763 _UpdateStateClose(item, location, buttons);
1767 if (action != NULL)
1768 *action = fState;
1770 // keyboard Enter will set this
1771 if (fChosenItem != NULL)
1772 item = fChosenItem;
1773 else if (fSelected == NULL) {
1774 // needed to cover (rare) mouse/ESC combination
1775 item = NULL;
1778 if (fSelected != NULL && LockLooper()) {
1779 _SelectItem(NULL);
1780 UnlockLooper();
1783 // delete the menu window recycled for all the child menus
1784 _DeleteMenuWindow();
1786 return item;
1790 void
1791 BMenu::_UpdateNavigationArea(BPoint position, BRect& navAreaRectAbove,
1792 BRect& navAreaRectBelow)
1794 #define NAV_AREA_THRESHOLD 8
1796 // The navigation area is a region in which mouse-overs won't select
1797 // the item under the cursor. This makes it easier to navigate to
1798 // submenus, as the cursor can be moved to submenu items directly instead
1799 // of having to move it horizontally into the submenu first. The concept
1800 // is illustrated below:
1802 // +-------+----+---------+
1803 // | | /| |
1804 // | | /*| |
1805 // |[2]--> | /**| |
1806 // | |/[4]| |
1807 // |------------| |
1808 // | [1] | [6] |
1809 // |------------| |
1810 // | |\[5]| |
1811 // |[3]--> | \**| |
1812 // | | \*| |
1813 // | | \| |
1814 // | +----|---------+
1815 // | |
1816 // +------------+
1818 // [1] Selected item, cursor position ('position')
1819 // [2] Upper navigation area rectangle ('navAreaRectAbove')
1820 // [3] Lower navigation area rectangle ('navAreaRectBelow')
1821 // [4] Upper navigation area
1822 // [5] Lower navigation area
1823 // [6] Submenu
1825 // The rectangles are used to calculate if the cursor is in the actual
1826 // navigation area (see _UpdateStateOpenSelect()).
1828 if (fSelected == NULL)
1829 return;
1831 BMenu* submenu = fSelected->Submenu();
1833 if (submenu != NULL) {
1834 BRect menuBounds = ConvertToScreen(Bounds());
1836 fSelected->Submenu()->LockLooper();
1837 BRect submenuBounds = fSelected->Submenu()->ConvertToScreen(
1838 fSelected->Submenu()->Bounds());
1839 fSelected->Submenu()->UnlockLooper();
1841 if (menuBounds.left < submenuBounds.left) {
1842 navAreaRectAbove.Set(position.x + NAV_AREA_THRESHOLD,
1843 submenuBounds.top, menuBounds.right,
1844 position.y);
1845 navAreaRectBelow.Set(position.x + NAV_AREA_THRESHOLD,
1846 position.y, menuBounds.right,
1847 submenuBounds.bottom);
1848 } else {
1849 navAreaRectAbove.Set(menuBounds.left,
1850 submenuBounds.top, position.x - NAV_AREA_THRESHOLD,
1851 position.y);
1852 navAreaRectBelow.Set(menuBounds.left,
1853 position.y, position.x - NAV_AREA_THRESHOLD,
1854 submenuBounds.bottom);
1856 } else {
1857 navAreaRectAbove = BRect();
1858 navAreaRectBelow = BRect();
1863 void
1864 BMenu::_UpdateStateOpenSelect(BMenuItem* item, BPoint position,
1865 BRect& navAreaRectAbove, BRect& navAreaRectBelow, bigtime_t& selectedTime,
1866 bigtime_t& navigationAreaTime)
1868 if (fState == MENU_STATE_CLOSED)
1869 return;
1871 if (item != fSelected) {
1872 if (navigationAreaTime == 0)
1873 navigationAreaTime = system_time();
1875 position = ConvertToScreen(position);
1877 bool inNavAreaRectAbove = navAreaRectAbove.Contains(position);
1878 bool inNavAreaRectBelow = navAreaRectBelow.Contains(position);
1880 if (fSelected == NULL
1881 || (!inNavAreaRectAbove && !inNavAreaRectBelow)) {
1882 _SelectItem(item, false);
1883 navAreaRectAbove = BRect();
1884 navAreaRectBelow = BRect();
1885 selectedTime = system_time();
1886 navigationAreaTime = 0;
1887 return;
1890 BRect menuBounds = ConvertToScreen(Bounds());
1892 fSelected->Submenu()->LockLooper();
1893 BRect submenuBounds = fSelected->Submenu()->ConvertToScreen(
1894 fSelected->Submenu()->Bounds());
1895 fSelected->Submenu()->UnlockLooper();
1897 float xOffset;
1899 // navAreaRectAbove and navAreaRectBelow have the same X
1900 // position and width, so it doesn't matter which one we use to
1901 // calculate the X offset
1902 if (menuBounds.left < submenuBounds.left)
1903 xOffset = position.x - navAreaRectAbove.left;
1904 else
1905 xOffset = navAreaRectAbove.right - position.x;
1907 bool inNavArea;
1909 if (inNavAreaRectAbove) {
1910 float yOffset = navAreaRectAbove.bottom - position.y;
1911 float ratio = navAreaRectAbove.Width() / navAreaRectAbove.Height();
1913 inNavArea = yOffset <= xOffset / ratio;
1914 } else {
1915 float yOffset = navAreaRectBelow.bottom - position.y;
1916 float ratio = navAreaRectBelow.Width() / navAreaRectBelow.Height();
1918 inNavArea = yOffset >= (navAreaRectBelow.Height() - xOffset
1919 / ratio);
1922 bigtime_t systime = system_time();
1924 if (!inNavArea || (navigationAreaTime > 0 && systime -
1925 navigationAreaTime > kNavigationAreaTimeout)) {
1926 // Don't delay opening of submenu if the user had
1927 // to wait for the navigation area timeout anyway
1928 _SelectItem(item, inNavArea);
1930 if (inNavArea) {
1931 _UpdateNavigationArea(position, navAreaRectAbove,
1932 navAreaRectBelow);
1933 } else {
1934 navAreaRectAbove = BRect();
1935 navAreaRectBelow = BRect();
1938 selectedTime = system_time();
1939 navigationAreaTime = 0;
1941 } else if (fSelected->Submenu() != NULL &&
1942 system_time() - selectedTime > kOpenSubmenuDelay) {
1943 _SelectItem(fSelected, true);
1945 if (!navAreaRectAbove.IsValid() && !navAreaRectBelow.IsValid()) {
1946 position = ConvertToScreen(position);
1947 _UpdateNavigationArea(position, navAreaRectAbove,
1948 navAreaRectBelow);
1952 if (fState != MENU_STATE_TRACKING)
1953 fState = MENU_STATE_TRACKING;
1957 void
1958 BMenu::_UpdateStateClose(BMenuItem* item, const BPoint& where,
1959 const uint32& buttons)
1961 if (fState == MENU_STATE_CLOSED)
1962 return;
1964 if (buttons != 0 && _IsStickyMode()) {
1965 if (item == NULL) {
1966 if (item != fSelected) {
1967 LockLooper();
1968 _SelectItem(item, false);
1969 UnlockLooper();
1971 fState = MENU_STATE_CLOSED;
1972 } else
1973 _SetStickyMode(false);
1974 } else if (buttons == 0 && !_IsStickyMode()) {
1975 if (fExtraRect != NULL && fExtraRect->Contains(where)) {
1976 _SetStickyMode(true);
1977 fExtraRect = NULL;
1978 // Setting this to NULL will prevent this code
1979 // to be executed next time
1980 } else {
1981 if (item != fSelected) {
1982 LockLooper();
1983 _SelectItem(item, false);
1984 UnlockLooper();
1986 fState = MENU_STATE_CLOSED;
1992 bool
1993 BMenu::_AddItem(BMenuItem* item, int32 index)
1995 ASSERT(item != NULL);
1996 if (index < 0 || index > fItems.CountItems())
1997 return false;
1999 if (item->IsMarked())
2000 _ItemMarked(item);
2002 if (!fItems.AddItem(item, index))
2003 return false;
2005 // install the item on the supermenu's window
2006 // or onto our window, if we are a root menu
2007 BWindow* window = NULL;
2008 if (Superitem() != NULL)
2009 window = Superitem()->fWindow;
2010 else
2011 window = Window();
2012 if (window != NULL)
2013 item->Install(window);
2015 item->SetSuper(this);
2016 return true;
2020 bool
2021 BMenu::_RemoveItems(int32 index, int32 count, BMenuItem* item,
2022 bool deleteItems)
2024 bool success = false;
2025 bool invalidateLayout = false;
2027 bool locked = LockLooper();
2028 BWindow* window = Window();
2030 // The plan is simple: If we're given a BMenuItem directly, we use it
2031 // and ignore index and count. Otherwise, we use them instead.
2032 if (item != NULL) {
2033 if (fItems.RemoveItem(item)) {
2034 if (item == fSelected && window != NULL)
2035 _SelectItem(NULL);
2036 item->Uninstall();
2037 item->SetSuper(NULL);
2038 if (deleteItems)
2039 delete item;
2040 success = invalidateLayout = true;
2042 } else {
2043 // We iterate backwards because it's simpler
2044 int32 i = std::min(index + count - 1, fItems.CountItems() - 1);
2045 // NOTE: the range check for "index" is done after
2046 // calculating the last index to be removed, so
2047 // that the range is not "shifted" unintentionally
2048 index = std::max((int32)0, index);
2049 for (; i >= index; i--) {
2050 item = static_cast<BMenuItem*>(fItems.ItemAt(i));
2051 if (item != NULL) {
2052 if (fItems.RemoveItem(item)) {
2053 if (item == fSelected && window != NULL)
2054 _SelectItem(NULL);
2055 item->Uninstall();
2056 item->SetSuper(NULL);
2057 if (deleteItems)
2058 delete item;
2059 success = true;
2060 invalidateLayout = true;
2061 } else {
2062 // operation not entirely successful
2063 success = false;
2064 break;
2070 if (invalidateLayout) {
2071 InvalidateLayout();
2072 if (locked && window != NULL) {
2073 _LayoutItems(0);
2074 _UpdateWindowViewSize(false);
2075 Invalidate();
2079 if (locked)
2080 UnlockLooper();
2082 return success;
2086 bool
2087 BMenu::_RelayoutIfNeeded()
2089 if (!fUseCachedMenuLayout) {
2090 fUseCachedMenuLayout = true;
2091 _CacheFontInfo();
2092 _LayoutItems(0);
2093 return true;
2095 return false;
2099 void
2100 BMenu::_LayoutItems(int32 index)
2102 _CalcTriggers();
2104 float width;
2105 float height;
2106 _ComputeLayout(index, fResizeToFit, true, &width, &height);
2108 if (fResizeToFit)
2109 ResizeTo(width, height);
2113 BSize
2114 BMenu::_ValidatePreferredSize()
2116 if (!fLayoutData->preferred.IsWidthSet() || ResizingMode()
2117 != fLayoutData->lastResizingMode) {
2118 _ComputeLayout(0, true, false, NULL, NULL);
2119 ResetLayoutInvalidation();
2122 return fLayoutData->preferred;
2126 void
2127 BMenu::_ComputeLayout(int32 index, bool bestFit, bool moveItems,
2128 float* _width, float* _height)
2130 // TODO: Take "bestFit", "moveItems", "index" into account,
2131 // Recalculate only the needed items,
2132 // not the whole layout every time
2134 fLayoutData->lastResizingMode = ResizingMode();
2136 BRect frame;
2137 switch (fLayout) {
2138 case B_ITEMS_IN_COLUMN:
2140 BRect parentFrame;
2141 BRect* overrideFrame = NULL;
2142 if (dynamic_cast<_BMCMenuBar_*>(Supermenu()) != NULL) {
2143 // When the menu is modified while it's open, we get here in a
2144 // situation where trying to lock the looper would deadlock
2145 // (the window is locked waiting for the menu to terminate).
2146 // In that case, just give up on getting the supermenu bounds
2147 // and keep the menu at the current width and position.
2148 if (Supermenu()->LockLooperWithTimeout(0) == B_OK) {
2149 parentFrame = Supermenu()->Bounds();
2150 Supermenu()->UnlockLooper();
2151 overrideFrame = &parentFrame;
2155 _ComputeColumnLayout(index, bestFit, moveItems, overrideFrame,
2156 frame);
2157 break;
2160 case B_ITEMS_IN_ROW:
2161 _ComputeRowLayout(index, bestFit, moveItems, frame);
2162 break;
2164 case B_ITEMS_IN_MATRIX:
2165 _ComputeMatrixLayout(frame);
2166 break;
2169 // change width depending on resize mode
2170 BSize size;
2171 if ((ResizingMode() & B_FOLLOW_LEFT_RIGHT) == B_FOLLOW_LEFT_RIGHT) {
2172 if (dynamic_cast<_BMCMenuBar_*>(this) != NULL)
2173 size.width = Bounds().Width() - fPad.right;
2174 else if (Parent() != NULL)
2175 size.width = Parent()->Frame().Width() + 1;
2176 else if (Window() != NULL)
2177 size.width = Window()->Frame().Width() + 1;
2178 else
2179 size.width = Bounds().Width();
2180 } else
2181 size.width = frame.Width();
2183 size.height = frame.Height();
2185 if (_width)
2186 *_width = size.width;
2188 if (_height)
2189 *_height = size.height;
2191 if (bestFit)
2192 fLayoutData->preferred = size;
2194 if (moveItems)
2195 fUseCachedMenuLayout = true;
2199 void
2200 BMenu::_ComputeColumnLayout(int32 index, bool bestFit, bool moveItems,
2201 BRect* overrideFrame, BRect& frame)
2203 bool command = false;
2204 bool control = false;
2205 bool shift = false;
2206 bool option = false;
2208 if (index > 0)
2209 frame = ItemAt(index - 1)->Frame();
2210 else if (overrideFrame != NULL)
2211 frame.Set(0, 0, overrideFrame->right, -1);
2212 else
2213 frame.Set(0, 0, 0, -1);
2215 BFont font;
2216 GetFont(&font);
2218 for (; index < fItems.CountItems(); index++) {
2219 BMenuItem* item = ItemAt(index);
2221 float width;
2222 float height;
2223 item->GetContentSize(&width, &height);
2225 if (item->fModifiers && item->fShortcutChar) {
2226 width += font.Size();
2227 if ((item->fModifiers & B_COMMAND_KEY) != 0)
2228 command = true;
2230 if ((item->fModifiers & B_CONTROL_KEY) != 0)
2231 control = true;
2233 if ((item->fModifiers & B_SHIFT_KEY) != 0)
2234 shift = true;
2236 if ((item->fModifiers & B_OPTION_KEY) != 0)
2237 option = true;
2240 item->fBounds.left = 0.0f;
2241 item->fBounds.top = frame.bottom + 1.0f;
2242 item->fBounds.bottom = item->fBounds.top + height + fPad.top
2243 + fPad.bottom;
2245 if (item->fSubmenu != NULL)
2246 width += item->Frame().Height();
2248 frame.right = std::max(frame.right, width + fPad.left + fPad.right);
2249 frame.bottom = item->fBounds.bottom;
2252 if (command) {
2253 frame.right
2254 += BPrivate::MenuPrivate::MenuItemCommand()->Bounds().Width() + 1;
2256 if (control) {
2257 frame.right
2258 += BPrivate::MenuPrivate::MenuItemControl()->Bounds().Width() + 1;
2260 if (option) {
2261 frame.right
2262 += BPrivate::MenuPrivate::MenuItemOption()->Bounds().Width() + 1;
2264 if (shift) {
2265 frame.right
2266 += BPrivate::MenuPrivate::MenuItemShift()->Bounds().Width() + 1;
2269 if (fMaxContentWidth > 0)
2270 frame.right = std::min(frame.right, fMaxContentWidth);
2272 if (moveItems) {
2273 for (int32 i = 0; i < fItems.CountItems(); i++)
2274 ItemAt(i)->fBounds.right = frame.right;
2277 frame.top = 0;
2278 frame.right = ceilf(frame.right);
2282 void
2283 BMenu::_ComputeRowLayout(int32 index, bool bestFit, bool moveItems,
2284 BRect& frame)
2286 font_height fh;
2287 GetFontHeight(&fh);
2288 frame.Set(0.0f, 0.0f, 0.0f, ceilf(fh.ascent + fh.descent + fPad.top
2289 + fPad.bottom));
2291 for (int32 i = 0; i < fItems.CountItems(); i++) {
2292 BMenuItem* item = ItemAt(i);
2294 float width, height;
2295 item->GetContentSize(&width, &height);
2297 item->fBounds.left = frame.right;
2298 item->fBounds.top = 0.0f;
2299 item->fBounds.right = item->fBounds.left + width + fPad.left
2300 + fPad.right;
2302 frame.right = item->Frame().right + 1.0f;
2303 frame.bottom = std::max(frame.bottom, height + fPad.top + fPad.bottom);
2306 if (moveItems) {
2307 for (int32 i = 0; i < fItems.CountItems(); i++)
2308 ItemAt(i)->fBounds.bottom = frame.bottom;
2311 if (bestFit)
2312 frame.right = ceilf(frame.right);
2313 else
2314 frame.right = Bounds().right;
2318 void
2319 BMenu::_ComputeMatrixLayout(BRect &frame)
2321 frame.Set(0, 0, 0, 0);
2322 for (int32 i = 0; i < CountItems(); i++) {
2323 BMenuItem* item = ItemAt(i);
2324 if (item != NULL) {
2325 frame.left = std::min(frame.left, item->Frame().left);
2326 frame.right = std::max(frame.right, item->Frame().right);
2327 frame.top = std::min(frame.top, item->Frame().top);
2328 frame.bottom = std::max(frame.bottom, item->Frame().bottom);
2334 void
2335 BMenu::LayoutInvalidated(bool descendants)
2337 fUseCachedMenuLayout = false;
2338 fLayoutData->preferred.Set(B_SIZE_UNSET, B_SIZE_UNSET);
2342 // Assumes the SuperMenu to be locked (due to calling ConvertToScreen())
2343 BPoint
2344 BMenu::ScreenLocation()
2346 BMenu* superMenu = Supermenu();
2347 BMenuItem* superItem = Superitem();
2349 if (superMenu == NULL || superItem == NULL) {
2350 debugger("BMenu can't determine where to draw."
2351 "Override BMenu::ScreenLocation() to determine location.");
2354 BPoint point;
2355 if (superMenu->Layout() == B_ITEMS_IN_COLUMN)
2356 point = superItem->Frame().RightTop() + BPoint(1.0f, 1.0f);
2357 else
2358 point = superItem->Frame().LeftBottom() + BPoint(1.0f, 1.0f);
2360 superMenu->ConvertToScreen(&point);
2362 return point;
2366 BRect
2367 BMenu::_CalcFrame(BPoint where, bool* scrollOn)
2369 // TODO: Improve me
2370 BRect bounds = Bounds();
2371 BRect frame = bounds.OffsetToCopy(where);
2373 BScreen screen(Window());
2374 BRect screenFrame = screen.Frame();
2376 BMenu* superMenu = Supermenu();
2377 BMenuItem* superItem = Superitem();
2379 // TODO: Horrible hack:
2380 // When added to a BMenuField, a BPopUpMenu is the child of
2381 // a _BMCMenuBar_ to "fake" the menu hierarchy
2382 bool inMenuField = dynamic_cast<_BMCMenuBar_*>(superMenu) != NULL;
2383 bool scroll = false;
2384 if (superMenu == NULL || superItem == NULL || inMenuField) {
2385 // just move the window on screen
2387 if (frame.bottom > screenFrame.bottom)
2388 frame.OffsetBy(0, screenFrame.bottom - frame.bottom);
2389 else if (frame.top < screenFrame.top)
2390 frame.OffsetBy(0, -frame.top);
2392 if (frame.right > screenFrame.right)
2393 frame.OffsetBy(screenFrame.right - frame.right, 0);
2394 else if (frame.left < screenFrame.left)
2395 frame.OffsetBy(-frame.left, 0);
2396 } else if (superMenu->Layout() == B_ITEMS_IN_COLUMN) {
2397 if (frame.right > screenFrame.right)
2398 frame.OffsetBy(-superItem->Frame().Width() - frame.Width() - 2, 0);
2400 if (frame.left < 0)
2401 frame.OffsetBy(-frame.left + 6, 0);
2403 if (frame.bottom > screenFrame.bottom)
2404 frame.OffsetBy(0, screenFrame.bottom - frame.bottom);
2405 } else {
2406 if (frame.bottom > screenFrame.bottom) {
2407 if (scrollOn != NULL && superMenu != NULL
2408 && dynamic_cast<BMenuBar*>(superMenu) != NULL
2409 && frame.top < (screenFrame.bottom - 80)) {
2410 scroll = true;
2411 } else {
2412 frame.OffsetBy(0, -superItem->Frame().Height()
2413 - frame.Height() - 3);
2417 if (frame.right > screenFrame.right)
2418 frame.OffsetBy(screenFrame.right - frame.right, 0);
2421 if (!scroll) {
2422 // basically, if this returns false, it means
2423 // that the menu frame won't fit completely inside the screen
2424 // TODO: Scrolling will currently only work up/down,
2425 // not left/right
2426 scroll = screenFrame.Height() < frame.Height();
2429 if (scrollOn != NULL)
2430 *scrollOn = scroll;
2432 return frame;
2436 void
2437 BMenu::_DrawItems(BRect updateRect)
2439 int32 itemCount = fItems.CountItems();
2440 for (int32 i = 0; i < itemCount; i++) {
2441 BMenuItem* item = ItemAt(i);
2442 if (item->Frame().Intersects(updateRect))
2443 item->Draw();
2449 BMenu::_State(BMenuItem** item) const
2451 if (fState == MENU_STATE_TRACKING || fState == MENU_STATE_CLOSED)
2452 return fState;
2454 if (fSelected != NULL && fSelected->Submenu() != NULL)
2455 return fSelected->Submenu()->_State(item);
2457 return fState;
2461 void
2462 BMenu::_InvokeItem(BMenuItem* item, bool now)
2464 if (!item->IsEnabled())
2465 return;
2467 // Do the "selected" animation
2468 // TODO: Doesn't work. This is supposed to highlight
2469 // and dehighlight the item, works on beos but not on haiku.
2470 if (!item->Submenu() && LockLooper()) {
2471 snooze(50000);
2472 item->Select(true);
2473 Window()->UpdateIfNeeded();
2474 snooze(50000);
2475 item->Select(false);
2476 Window()->UpdateIfNeeded();
2477 snooze(50000);
2478 item->Select(true);
2479 Window()->UpdateIfNeeded();
2480 snooze(50000);
2481 item->Select(false);
2482 Window()->UpdateIfNeeded();
2483 UnlockLooper();
2486 // Lock the root menu window before calling BMenuItem::Invoke()
2487 BMenu* parent = this;
2488 BMenu* rootMenu = NULL;
2489 do {
2490 rootMenu = parent;
2491 parent = rootMenu->Supermenu();
2492 } while (parent != NULL);
2494 if (rootMenu->LockLooper()) {
2495 item->Invoke();
2496 rootMenu->UnlockLooper();
2501 bool
2502 BMenu::_OverSuper(BPoint location)
2504 if (!Supermenu())
2505 return false;
2507 return fSuperbounds.Contains(location);
2511 bool
2512 BMenu::_OverSubmenu(BMenuItem* item, BPoint loc)
2514 if (item == NULL)
2515 return false;
2517 BMenu* subMenu = item->Submenu();
2518 if (subMenu == NULL || subMenu->Window() == NULL)
2519 return false;
2521 // assume that loc is in screen coordinates
2522 if (subMenu->Window()->Frame().Contains(loc))
2523 return true;
2525 return subMenu->_OverSubmenu(subMenu->fSelected, loc);
2529 BMenuWindow*
2530 BMenu::_MenuWindow()
2532 #if USE_CACHED_MENUWINDOW
2533 if (fCachedMenuWindow == NULL) {
2534 char windowName[64];
2535 snprintf(windowName, 64, "%s cached menu", Name());
2536 fCachedMenuWindow = new (nothrow) BMenuWindow(windowName);
2538 #endif
2539 return fCachedMenuWindow;
2543 void
2544 BMenu::_DeleteMenuWindow()
2546 if (fCachedMenuWindow != NULL) {
2547 fCachedMenuWindow->Lock();
2548 fCachedMenuWindow->Quit();
2549 fCachedMenuWindow = NULL;
2554 BMenuItem*
2555 BMenu::_HitTestItems(BPoint where, BPoint slop) const
2557 // TODO: Take "slop" into account ?
2559 // if the point doesn't lie within the menu's
2560 // bounds, bail out immediately
2561 if (!Bounds().Contains(where))
2562 return NULL;
2564 int32 itemCount = CountItems();
2565 for (int32 i = 0; i < itemCount; i++) {
2566 BMenuItem* item = ItemAt(i);
2567 if (item->Frame().Contains(where)
2568 && dynamic_cast<BSeparatorItem*>(item) == NULL) {
2569 return item;
2573 return NULL;
2577 BRect
2578 BMenu::_Superbounds() const
2580 return fSuperbounds;
2584 void
2585 BMenu::_CacheFontInfo()
2587 font_height fh;
2588 GetFontHeight(&fh);
2589 fAscent = fh.ascent;
2590 fDescent = fh.descent;
2591 fFontHeight = ceilf(fh.ascent + fh.descent + fh.leading);
2595 void
2596 BMenu::_ItemMarked(BMenuItem* item)
2598 if (IsRadioMode()) {
2599 for (int32 i = 0; i < CountItems(); i++) {
2600 if (ItemAt(i) != item)
2601 ItemAt(i)->SetMarked(false);
2605 if (IsLabelFromMarked() && Superitem())
2606 Superitem()->SetLabel(item->Label());
2610 void
2611 BMenu::_Install(BWindow* target)
2613 for (int32 i = 0; i < CountItems(); i++)
2614 ItemAt(i)->Install(target);
2618 void
2619 BMenu::_Uninstall()
2621 for (int32 i = 0; i < CountItems(); i++)
2622 ItemAt(i)->Uninstall();
2626 void
2627 BMenu::_SelectItem(BMenuItem* item, bool showSubmenu, bool selectFirstItem,
2628 bool keyDown)
2630 // Avoid deselecting and then reselecting the same item
2631 // which would cause flickering
2632 if (item != fSelected) {
2633 if (fSelected != NULL) {
2634 fSelected->Select(false);
2635 BMenu* subMenu = fSelected->Submenu();
2636 if (subMenu != NULL && subMenu->Window() != NULL)
2637 subMenu->_Hide();
2640 fSelected = item;
2641 if (fSelected != NULL)
2642 fSelected->Select(true);
2645 if (fSelected != NULL && showSubmenu) {
2646 BMenu* subMenu = fSelected->Submenu();
2647 if (subMenu != NULL && subMenu->Window() == NULL) {
2648 if (!subMenu->_Show(selectFirstItem, keyDown)) {
2649 // something went wrong, deselect the item
2650 fSelected->Select(false);
2651 fSelected = NULL;
2658 bool
2659 BMenu::_SelectNextItem(BMenuItem* item, bool forward)
2661 if (CountItems() == 0) // cannot select next item in an empty menu
2662 return false;
2664 BMenuItem* nextItem = _NextItem(item, forward);
2665 if (nextItem == NULL)
2666 return false;
2668 _SelectItem(nextItem, dynamic_cast<BMenuBar*>(this) != NULL);
2670 if (LockLooper()) {
2671 be_app->ObscureCursor();
2672 UnlockLooper();
2675 return true;
2679 BMenuItem*
2680 BMenu::_NextItem(BMenuItem* item, bool forward) const
2682 const int32 numItems = fItems.CountItems();
2683 if (numItems == 0)
2684 return NULL;
2686 int32 index = fItems.IndexOf(item);
2687 int32 loopCount = numItems;
2688 while (--loopCount) {
2689 // Cycle through menu items in the given direction...
2690 if (forward)
2691 index++;
2692 else
2693 index--;
2695 // ... wrap around...
2696 if (index < 0)
2697 index = numItems - 1;
2698 else if (index >= numItems)
2699 index = 0;
2701 // ... and return the first suitable item found.
2702 BMenuItem* nextItem = ItemAt(index);
2703 if (nextItem->IsEnabled())
2704 return nextItem;
2707 // If no other suitable item was found, return NULL.
2708 return NULL;
2712 void
2713 BMenu::_SetIgnoreHidden(bool on)
2715 fIgnoreHidden = on;
2719 void
2720 BMenu::_SetStickyMode(bool on)
2722 if (fStickyMode == on)
2723 return;
2725 fStickyMode = on;
2727 if (fSuper != NULL) {
2728 // propagate the status to the super menu
2729 fSuper->_SetStickyMode(on);
2730 } else {
2731 // TODO: Ugly hack, but it needs to be done in this method
2732 BMenuBar* menuBar = dynamic_cast<BMenuBar*>(this);
2733 if (on && menuBar != NULL && menuBar->LockLooper()) {
2734 // If we are switching to sticky mode,
2735 // steal the focus from the current focus view
2736 // (needed to handle keyboard navigation)
2737 menuBar->_StealFocus();
2738 menuBar->UnlockLooper();
2744 bool
2745 BMenu::_IsStickyMode() const
2747 return fStickyMode;
2751 void
2752 BMenu::_GetShiftKey(uint32 &value) const
2754 // TODO: Move into init_interface_kit().
2755 // Currently we can't do that, as get_modifier_key() blocks forever
2756 // when called on input_server initialization, since it tries
2757 // to send a synchronous message to itself (input_server is
2758 // a BApplication)
2760 if (get_modifier_key(B_LEFT_SHIFT_KEY, &value) != B_OK)
2761 value = 0x4b;
2765 void
2766 BMenu::_GetControlKey(uint32 &value) const
2768 // TODO: Move into init_interface_kit().
2769 // Currently we can't do that, as get_modifier_key() blocks forever
2770 // when called on input_server initialization, since it tries
2771 // to send a synchronous message to itself (input_server is
2772 // a BApplication)
2774 if (get_modifier_key(B_LEFT_CONTROL_KEY, &value) != B_OK)
2775 value = 0x5c;
2779 void
2780 BMenu::_GetCommandKey(uint32 &value) const
2782 // TODO: Move into init_interface_kit().
2783 // Currently we can't do that, as get_modifier_key() blocks forever
2784 // when called on input_server initialization, since it tries
2785 // to send a synchronous message to itself (input_server is
2786 // a BApplication)
2788 if (get_modifier_key(B_LEFT_COMMAND_KEY, &value) != B_OK)
2789 value = 0x66;
2793 void
2794 BMenu::_GetOptionKey(uint32 &value) const
2796 // TODO: Move into init_interface_kit().
2797 // Currently we can't do that, as get_modifier_key() blocks forever
2798 // when called on input_server initialization, since it tries
2799 // to send a synchronous message to itself (input_server is
2800 // a BApplication)
2802 if (get_modifier_key(B_LEFT_OPTION_KEY, &value) != B_OK)
2803 value = 0x5d;
2807 void
2808 BMenu::_GetMenuKey(uint32 &value) const
2810 // TODO: Move into init_interface_kit().
2811 // Currently we can't do that, as get_modifier_key() blocks forever
2812 // when called on input_server initialization, since it tries
2813 // to send a synchronous message to itself (input_server is
2814 // a BApplication)
2816 if (get_modifier_key(B_MENU_KEY, &value) != B_OK)
2817 value = 0x68;
2821 void
2822 BMenu::_CalcTriggers()
2824 BPrivate::TriggerList triggerList;
2826 // Gathers the existing triggers set by the user
2827 for (int32 i = 0; i < CountItems(); i++) {
2828 char trigger = ItemAt(i)->Trigger();
2829 if (trigger != 0)
2830 triggerList.AddTrigger(trigger);
2833 // Set triggers for items which don't have one yet
2834 for (int32 i = 0; i < CountItems(); i++) {
2835 BMenuItem* item = ItemAt(i);
2836 if (item->Trigger() == 0) {
2837 uint32 trigger;
2838 int32 index;
2839 if (_ChooseTrigger(item->Label(), index, trigger, triggerList))
2840 item->SetAutomaticTrigger(index, trigger);
2846 bool
2847 BMenu::_ChooseTrigger(const char* title, int32& index, uint32& trigger,
2848 BPrivate::TriggerList& triggers)
2850 if (title == NULL)
2851 return false;
2853 uint32 c;
2855 // two runs: first we look out for uppercase letters
2856 // TODO: support Unicode characters correctly!
2857 for (uint32 i = 0; (c = title[i]) != '\0'; i++) {
2858 if (!IsInsideGlyph(c) && isupper(c) && !triggers.HasTrigger(c)) {
2859 index = i;
2860 trigger = tolower(c);
2861 return triggers.AddTrigger(c);
2865 // then, if we still haven't found anything, we accept them all
2866 index = 0;
2867 while ((c = UTF8ToCharCode(&title)) != 0) {
2868 if (!isspace(c) && !triggers.HasTrigger(c)) {
2869 trigger = tolower(c);
2870 return triggers.AddTrigger(c);
2873 index++;
2876 return false;
2880 void
2881 BMenu::_UpdateWindowViewSize(const bool &move)
2883 BMenuWindow* window = static_cast<BMenuWindow*>(Window());
2884 if (window == NULL)
2885 return;
2887 if (dynamic_cast<BMenuBar*>(this) != NULL)
2888 return;
2890 if (!fResizeToFit)
2891 return;
2893 bool scroll = false;
2894 const BPoint screenLocation = move ? ScreenLocation()
2895 : window->Frame().LeftTop();
2896 BRect frame = _CalcFrame(screenLocation, &scroll);
2897 ResizeTo(frame.Width(), frame.Height());
2899 if (fItems.CountItems() > 0) {
2900 if (!scroll) {
2901 window->ResizeTo(Bounds().Width(), Bounds().Height());
2902 } else {
2903 BScreen screen(window);
2905 // If we need scrolling, resize the window to fit the screen and
2906 // attach scrollers to our cached BMenuWindow.
2907 if (dynamic_cast<BMenuBar*>(Supermenu()) == NULL || frame.top < 0) {
2908 window->ResizeTo(Bounds().Width(), screen.Frame().Height());
2909 frame.top = 0;
2910 } else {
2911 // Or, in case our parent was a BMenuBar enable scrolling with
2912 // normal size.
2913 window->ResizeTo(Bounds().Width(),
2914 screen.Frame().bottom - frame.top);
2917 if (fLayout == B_ITEMS_IN_COLUMN) {
2918 // we currently only support scrolling for B_ITEMS_IN_COLUMN
2919 window->AttachScrollers();
2921 BMenuItem* selectedItem = FindMarked();
2922 if (selectedItem != NULL) {
2923 // scroll to the selected item
2924 if (Supermenu() == NULL) {
2925 window->TryScrollTo(selectedItem->Frame().top);
2926 } else {
2927 BPoint point = selectedItem->Frame().LeftTop();
2928 BPoint superPoint = Superitem()->Frame().LeftTop();
2929 Supermenu()->ConvertToScreen(&superPoint);
2930 ConvertToScreen(&point);
2931 window->TryScrollTo(point.y - superPoint.y);
2936 } else {
2937 _CacheFontInfo();
2938 window->ResizeTo(StringWidth(BPrivate::kEmptyMenuLabel)
2939 + fPad.left + fPad.right,
2940 fFontHeight + fPad.top + fPad.bottom);
2943 if (move)
2944 window->MoveTo(frame.LeftTop());
2948 bool
2949 BMenu::_AddDynamicItems(bool keyDown)
2951 bool addAborted = false;
2952 if (AddDynamicItem(B_INITIAL_ADD)) {
2953 BMenuItem* superItem = Superitem();
2954 BMenu* superMenu = Supermenu();
2955 do {
2956 if (superMenu != NULL
2957 && !superMenu->_OkToProceed(superItem, keyDown)) {
2958 AddDynamicItem(B_ABORT);
2959 addAborted = true;
2960 break;
2962 } while (AddDynamicItem(B_PROCESSING));
2965 return addAborted;
2969 bool
2970 BMenu::_OkToProceed(BMenuItem* item, bool keyDown)
2972 BPoint where;
2973 uint32 buttons;
2974 GetMouse(&where, &buttons, false);
2975 bool stickyMode = _IsStickyMode();
2976 // Quit if user clicks the mouse button in sticky mode
2977 // or releases the mouse button in nonsticky mode
2978 // or moves the pointer over another item
2979 // TODO: I added the check for BMenuBar to solve a problem with Deskbar.
2980 // BeOS seems to do something similar. This could also be a bug in
2981 // Deskbar, though.
2982 if ((buttons != 0 && stickyMode)
2983 || ((dynamic_cast<BMenuBar*>(this) == NULL
2984 && (buttons == 0 && !stickyMode))
2985 || ((_HitTestItems(where) != item) && !keyDown))) {
2986 return false;
2989 return true;
2993 bool
2994 BMenu::_CustomTrackingWantsToQuit()
2996 if (fExtraMenuData != NULL && fExtraMenuData->trackingHook != NULL
2997 && fExtraMenuData->trackingState != NULL) {
2998 return fExtraMenuData->trackingHook(this,
2999 fExtraMenuData->trackingState);
3002 return false;
3006 void
3007 BMenu::_QuitTracking(bool onlyThis)
3009 _SelectItem(NULL);
3010 if (BMenuBar* menuBar = dynamic_cast<BMenuBar*>(this))
3011 menuBar->_RestoreFocus();
3013 fState = MENU_STATE_CLOSED;
3015 if (!onlyThis) {
3016 // Close the whole menu hierarchy
3017 if (Supermenu() != NULL)
3018 Supermenu()->fState = MENU_STATE_CLOSED;
3020 if (_IsStickyMode())
3021 _SetStickyMode(false);
3023 if (LockLooper()) {
3024 be_app->ShowCursor();
3025 UnlockLooper();
3029 _Hide();
3033 // #pragma mark - menu_info functions
3036 // TODO: Maybe the following two methods would fit better into
3037 // InterfaceDefs.cpp
3038 // In R5, they do all the work client side, we let the app_server handle the
3039 // details.
3040 status_t
3041 set_menu_info(menu_info* info)
3043 if (!info)
3044 return B_BAD_VALUE;
3046 BPrivate::AppServerLink link;
3047 link.StartMessage(AS_SET_MENU_INFO);
3048 link.Attach<menu_info>(*info);
3050 status_t status = B_ERROR;
3051 if (link.FlushWithReply(status) == B_OK && status == B_OK)
3052 BMenu::sMenuInfo = *info;
3053 // Update also the local copy, in case anyone relies on it
3055 return status;
3059 status_t
3060 get_menu_info(menu_info* info)
3062 if (!info)
3063 return B_BAD_VALUE;
3065 BPrivate::AppServerLink link;
3066 link.StartMessage(AS_GET_MENU_INFO);
3068 status_t status = B_ERROR;
3069 if (link.FlushWithReply(status) == B_OK && status == B_OK)
3070 link.Read<menu_info>(info);
3072 return status;
3076 extern "C" void
3077 B_IF_GCC_2(InvalidateLayout__5BMenub,_ZN5BMenu16InvalidateLayoutEb)(
3078 BMenu* menu, bool descendants)
3080 menu->InvalidateLayout();