2 * Copyright 2001-2013 Haiku, Inc. All rights reserved.
3 * Distributed under the terms of the MIT license.
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
22 #include <Application.h>
24 #include <ControlLook.h>
27 #include <FindDirectory.h>
29 #include <LayoutUtils.h>
32 #include <Messenger.h>
34 #include <PropertyInfo.h>
36 #include <ScrollBar.h>
37 #include <SystemCatalog.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"
58 #define B_TRANSLATE(str) \
59 gSystemCatalog.GetString(B_TRANSLATE_MARK(str), "Menu")
63 using BPrivate::BMenuWindow
;
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
)); }
86 menu_tracking_hook trackingHook
;
89 ExtraMenuData(menu_tracking_hook func
, void* state
)
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.",
115 { "Enabled", { B_SET_PROPERTY
, 0 },
116 { B_DIRECT_SPECIFIER
, 0 }, "Enables or disables menu or menu item.",
120 { "Label", { B_GET_PROPERTY
, 0 },
121 { B_DIRECT_SPECIFIER
, 0 }, "Returns the string label of the menu or "
126 { "Label", { B_SET_PROPERTY
, 0 },
127 { B_DIRECT_SPECIFIER
, 0 }, "Sets the string label of the menu or menu "
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.",
138 { "Mark", { B_SET_PROPERTY
, 0 },
139 { B_DIRECT_SPECIFIER
, 0 }, "Marks or unmarks the menu item or the "
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, {}
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 "
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."
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
{
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
),
219 fPad(14.0f
, 2.0f
, 20.0f
, 0.0f
),
221 fCachedMenuWindow(NULL
),
227 fState(MENU_STATE_CLOSED
),
230 fMaxContentWidth(0.0f
),
231 fInitMatrixSize(NULL
),
232 fExtraMenuData(NULL
),
235 fUseCachedMenuLayout(false),
239 fTrackNewBounds(false),
242 fTriggerEnabled(true),
243 fRedrawAfterSticky(false),
244 fAttachAborted(false)
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
),
255 fCachedMenuWindow(NULL
),
262 fLayout(B_ITEMS_IN_MATRIX
),
264 fMaxContentWidth(0.0f
),
265 fInitMatrixSize(NULL
),
266 fExtraMenuData(NULL
),
269 fUseCachedMenuLayout(false),
273 fTrackNewBounds(false),
276 fTriggerEnabled(true),
277 fRedrawAfterSticky(false),
278 fAttachAborted(false)
284 BMenu::BMenu(BMessage
* archive
)
288 fPad(14.0f
, 2.0f
, 20.0f
, 0.0f
),
290 fCachedMenuWindow(NULL
),
296 fState(MENU_STATE_CLOSED
),
297 fLayout(B_ITEMS_IN_ROW
),
299 fMaxContentWidth(0.0f
),
300 fInitMatrixSize(NULL
),
301 fExtraMenuData(NULL
),
304 fUseCachedMenuLayout(false),
308 fTrackNewBounds(false),
311 fTriggerEnabled(true),
312 fRedrawAfterSticky(false),
313 fAttachAborted(false)
323 RemoveItems(0, CountItems(), true);
325 delete fInitMatrixSize
;
326 delete fExtraMenuData
;
332 BMenu::Instantiate(BMessage
* archive
)
334 if (validate_instantiation(archive
, "BMenu"))
335 return new (nothrow
) BMenu(archive
);
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());
349 err
= data
->AddBool("_rsize_to_fit", fResizeToFit
);
351 err
= data
->AddBool("_disable", !IsEnabled());
353 err
= data
->AddBool("_radio", IsRadioMode());
355 err
= data
->AddBool("_trig_disabled", AreTriggersEnabled());
357 err
= data
->AddBool("_dyn_label", fDynamicName
);
359 err
= data
->AddFloat("_maxwidth", fMaxContentWidth
);
360 if (err
== B_OK
&& deep
) {
361 BMenuItem
* item
= NULL
;
363 while ((item
= ItemAt(index
++)) != NULL
) {
365 item
->Archive(&itemData
, deep
);
366 err
= data
->AddMessage("_items", &itemData
);
369 if (fLayout
== B_ITEMS_IN_MATRIX
) {
370 err
= data
->AddRect("_i_frames", item
->fBounds
);
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
) {
395 _UpdateWindowViewSize(false);
401 BMenu::DetachedFromWindow()
403 BView::DetachedFromWindow();
410 BView::AllAttached();
417 BView::AllDetached();
422 BMenu::Draw(BRect updateRect
)
424 if (_RelayoutIfNeeded()) {
429 DrawBackground(updateRect
);
430 _DrawItems(updateRect
);
435 BMenu::MessageReceived(BMessage
* message
)
437 switch (message
->what
) {
438 case B_MOUSE_WHEEL_CHANGED
:
441 message
->FindFloat("be:wheel_delta_y", &deltaY
);
445 BMenuWindow
* window
= dynamic_cast<BMenuWindow
*>(Window());
451 window
->GetSteps(&smallStep
, &largeStep
);
453 // pressing the shift key scrolls faster
454 if ((modifiers() & B_SHIFT_KEY
) != 0)
459 window
->TryScrollBy(deltaY
);
464 BView::MessageReceived(message
);
471 BMenu::KeyDown(const char* bytes
, int32 numBytes
)
473 // TODO: Test how it works on BeOS R5 and implement this correctly
476 if (fLayout
== B_ITEMS_IN_COLUMN
)
477 _SelectNextItem(fSelected
, false);
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);
493 if (fLayout
== B_ITEMS_IN_ROW
)
494 _SelectNextItem(fSelected
, false);
496 // this case has to be handled a bit specially.
497 BMenuItem
* item
= Superitem();
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());
507 fState
= MENU_STATE_KEY_LEAVE_SUBMENU
;
514 if (fLayout
== B_ITEMS_IN_ROW
)
515 _SelectNextItem(fSelected
, true);
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());
538 BMenuWindow
* window
= dynamic_cast<BMenuWindow
*>(Window());
539 if (window
== NULL
|| !window
->HasScrollers())
542 int32 deltaY
= bytes
[0] == B_PAGE_UP
? -1 : 1;
545 window
->GetSteps(NULL
, &largeStep
);
546 window
->TryScrollBy(deltaY
* largeStep
);
552 if (fSelected
!= NULL
) {
553 fChosenItem
= fSelected
;
554 // preserve for exit handling
555 _QuitTracking(false);
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());
567 _QuitTracking(false);
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
)
580 _QuitTracking(false);
592 _ValidatePreferredSize();
594 BSize size
= (GetLayout() != NULL
? GetLayout()->MinSize()
595 : fLayoutData
->preferred
);
597 return BLayoutUtils::ComposeSize(ExplicitMinSize(), size
);
604 _ValidatePreferredSize();
606 BSize size
= (GetLayout() != NULL
? GetLayout()->MaxSize()
607 : fLayoutData
->preferred
);
609 return BLayoutUtils::ComposeSize(ExplicitMaxSize(), size
);
614 BMenu::PreferredSize()
616 _ValidatePreferredSize();
618 BSize size
= (GetLayout() != NULL
? GetLayout()->PreferredSize()
619 : fLayoutData
->preferred
);
621 return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), size
);
626 BMenu::GetPreferredSize(float* _width
, float* _height
)
628 _ValidatePreferredSize();
631 *_width
= fLayoutData
->preferred
.width
;
634 *_height
= fLayoutData
->preferred
.height
;
639 BMenu::ResizeToPreferred()
641 BView::ResizeToPreferred();
648 // If the user set a layout, we let the base class version call its
650 if (GetLayout() != NULL
) {
655 if (_RelayoutIfNeeded())
661 BMenu::FrameMoved(BPoint new_position
)
663 BView::FrameMoved(new_position
);
668 BMenu::FrameResized(float new_width
, float new_height
)
670 BView::FrameResized(new_width
, new_height
);
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);
686 BMenu::MakeFocus(bool focused
)
688 BView::MakeFocus(focused
);
693 BMenu::AddItem(BMenuItem
* item
)
695 return AddItem(item
, CountItems());
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
))
712 if (!Window()->IsHidden()) {
714 _UpdateWindowViewSize(false);
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");
735 item
->fBounds
= frame
;
737 int32 index
= CountItems();
738 if (!_AddItem(item
, index
))
742 if (!Window()->IsHidden()) {
754 BMenu::AddItem(BMenu
* submenu
)
756 BMenuItem
* item
= new (nothrow
) BMenuItem(submenu
);
760 if (!AddItem(item
, CountItems())) {
761 item
->fSubmenu
= NULL
;
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
);
782 if (!AddItem(item
, index
)) {
783 item
->fSubmenu
= NULL
;
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
);
804 if (!AddItem(item
, frame
)) {
805 item
->fSubmenu
= NULL
;
815 BMenu::AddList(BList
* list
, int32 index
)
817 // TODO: test this function, it's not documented in the bebook.
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
));
827 if (!_AddItem(item
, index
+ i
))
833 if (locked
&& Window() != NULL
&& !Window()->IsHidden()) {
834 // Make sure we update the layout if needed.
836 _UpdateWindowViewSize(false);
848 BMenu::AddSeparatorItem()
850 BMenuItem
* item
= new (nothrow
) BSeparatorItem();
851 if (!item
|| !AddItem(item
, CountItems())) {
861 BMenu::RemoveItem(BMenuItem
* item
)
863 return _RemoveItems(0, 0, item
, false);
868 BMenu::RemoveItem(int32 index
)
870 BMenuItem
* item
= ItemAt(index
);
872 _RemoveItems(0, 0, item
, false);
878 BMenu::RemoveItems(int32 index
, int32 count
, bool deleteItems
)
880 return _RemoveItems(index
, count
, NULL
, deleteItems
);
885 BMenu::RemoveItem(BMenu
* submenu
)
887 for (int32 i
= 0; i
< fItems
.CountItems(); i
++) {
888 if (static_cast<BMenuItem
*>(fItems
.ItemAtFast(i
))->Submenu()
890 return _RemoveItems(i
, 1, NULL
, false);
899 BMenu::CountItems() const
901 return fItems
.CountItems();
906 BMenu::ItemAt(int32 index
) const
908 return static_cast<BMenuItem
*>(fItems
.ItemAt(index
));
913 BMenu::SubmenuAt(int32 index
) const
915 BMenuItem
* item
= static_cast<BMenuItem
*>(fItems
.ItemAt(index
));
916 return item
!= NULL
? item
->Submenu() : NULL
;
921 BMenu::IndexOf(BMenuItem
* item
) const
923 return fItems
.IndexOf(item
);
928 BMenu::IndexOf(BMenu
* submenu
) const
930 for (int32 i
= 0; i
< fItems
.CountItems(); i
++) {
931 if (ItemAt(i
)->Submenu() == submenu
)
940 BMenu::FindItem(const char* label
) const
942 BMenuItem
* item
= NULL
;
944 for (int32 i
= 0; i
< CountItems(); i
++) {
947 if (item
->Label() && strcmp(item
->Label(), label
) == 0)
950 if (item
->Submenu() != NULL
) {
951 item
= item
->Submenu()->FindItem(label
);
962 BMenu::FindItem(uint32 command
) const
964 BMenuItem
* item
= NULL
;
966 for (int32 i
= 0; i
< CountItems(); i
++) {
969 if (item
->Command() == command
)
972 if (item
->Submenu() != NULL
) {
973 item
= item
->Submenu()->FindItem(command
);
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
);
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
);
1012 BMenu::SetEnabled(bool enable
)
1014 if (fEnabled
== enable
)
1019 if (dynamic_cast<_BMCMenuBar_
*>(Supermenu()) != NULL
)
1020 Supermenu()->SetEnabled(enable
);
1023 fSuperitem
->SetEnabled(enable
);
1028 BMenu::SetRadioMode(bool on
)
1032 SetLabelFromMarked(false);
1037 BMenu::SetTriggersEnabled(bool enable
)
1039 fTriggerEnabled
= enable
;
1044 BMenu::SetMaxContentWidth(float width
)
1046 fMaxContentWidth
= width
;
1051 BMenu::SetLabelFromMarked(bool on
)
1060 BMenu::IsLabelFromMarked()
1062 return fDynamicName
;
1067 BMenu::IsEnabled() const
1072 return fSuper
? fSuper
->IsEnabled() : true ;
1077 BMenu::IsRadioMode() const
1084 BMenu::AreTriggersEnabled() const
1086 return fTriggerEnabled
;
1091 BMenu::IsRedrawAfterSticky() const
1093 return fRedrawAfterSticky
;
1098 BMenu::MaxContentWidth() const
1100 return fMaxContentWidth
;
1107 for (int32 i
= 0; i
< fItems
.CountItems(); i
++) {
1108 BMenuItem
* item
= ItemAt(i
);
1110 if (item
->IsMarked())
1119 BMenu::FindMarkedIndex()
1121 for (int32 i
= 0; i
< fItems
.CountItems(); i
++) {
1122 BMenuItem
* item
= ItemAt(i
);
1124 if (item
->IsMarked())
1133 BMenu::Supermenu() const
1140 BMenu::Superitem() const
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
)) {
1168 // TODO: redirect to menu
1178 // TODO: redirect to menuitem
1184 target
= BView::ResolveSpecifier(msg
, index
, specifier
, form
,
1192 BMenu::GetSupportedSuites(BMessage
* data
)
1197 status_t err
= data
->AddString("suites", "suite/vnd.Be-menu");
1202 BPropertyInfo
propertyInfo(sPropList
);
1203 err
= data
->AddFlat("messages", &propertyInfo
);
1208 return BView::GetSupportedSuites(data
);
1213 BMenu::Perform(perform_code code
, void* _data
)
1216 case PERFORM_CODE_MIN_SIZE
:
1217 ((perform_data_min_size
*)_data
)->return_value
1221 case PERFORM_CODE_MAX_SIZE
:
1222 ((perform_data_max_size
*)_data
)->return_value
1226 case PERFORM_CODE_PREFERRED_SIZE
:
1227 ((perform_data_preferred_size
*)_data
)->return_value
1228 = BMenu::PreferredSize();
1231 case PERFORM_CODE_LAYOUT_ALIGNMENT
:
1232 ((perform_data_layout_alignment
*)_data
)->return_value
1233 = BMenu::LayoutAlignment();
1236 case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH
:
1237 ((perform_data_has_height_for_width
*)_data
)->return_value
1238 = BMenu::HasHeightForWidth();
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
,
1250 case PERFORM_CODE_SET_LAYOUT
:
1252 perform_data_set_layout
* data
= (perform_data_set_layout
*)_data
;
1253 BMenu::SetLayout(data
->layout
);
1257 case PERFORM_CODE_LAYOUT_INVALIDATED
:
1259 perform_data_layout_invalidated
* data
1260 = (perform_data_layout_invalidated
*)_data
;
1261 BMenu::LayoutInvalidated(data
->descendants
);
1265 case PERFORM_CODE_DO_LAYOUT
:
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
),
1285 fCachedMenuWindow(NULL
),
1291 fState(MENU_STATE_CLOSED
),
1294 fMaxContentWidth(0.0f
),
1295 fInitMatrixSize(NULL
),
1296 fExtraMenuData(NULL
),
1298 fResizeToFit(resizeToFit
),
1299 fUseCachedMenuLayout(false),
1301 fDynamicName(false),
1303 fTrackNewBounds(false),
1305 fIgnoreHidden(true),
1306 fTriggerEnabled(true),
1307 fRedrawAfterSticky(false),
1308 fAttachAborted(false)
1315 BMenu::SetItemMargins(float left
, float top
, float right
, float bottom
)
1317 fPad
.Set(left
, top
, right
, bottom
);
1322 BMenu::GetItemMargins(float* _left
, float* _top
, float* _right
,
1323 float* _bottom
) const
1332 *_right
= fPad
.right
;
1334 if (_bottom
!= NULL
)
1335 *_bottom
= fPad
.bottom
;
1340 BMenu::Layout() const
1354 BMenu::Show(bool selectFirst
)
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
1378 if (clickToOpenRect
!= NULL
&& LockLooper()) {
1379 fExtraRect
= clickToOpenRect
;
1380 ConvertFromScreen(fExtraRect
);
1384 _SetStickyMode(sticky
);
1387 BMenuItem
* menuItem
= _Track(&action
);
1395 // #pragma mark - BMenu private methods
1399 BMenu::AddDynamicItem(add_state state
)
1401 // Implemented in subclasses
1407 BMenu::DrawBackground(BRect updateRect
)
1409 if (be_control_look
!= NULL
) {
1410 rgb_color base
= sMenuInfo
.background_color
;
1413 flags
|= BControlLook::B_DISABLED
;
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
;
1428 borders
|= BControlLook::B_TOP_BORDER
1429 | BControlLook::B_BOTTOM_BORDER
;
1431 be_control_look
->DrawMenuBackground(this, rect
, updateRect
, base
, 0,
1437 rgb_color oldColor
= HighColor();
1438 SetHighColor(sMenuInfo
.background_color
);
1439 FillRect(Bounds() & updateRect
, B_SOLID_HIGH
);
1440 SetHighColor(oldColor
);
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() {}
1459 BMenu::_InitData(BMessage
* archive
)
1461 BPrivate::kEmptyMenuLabel
= B_TRANSLATE("<empty>");
1463 // TODO: Get _color, _fname, _fflt from the message, if present
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
);
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
);
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
)) {
1497 if (fLayout
== B_ITEMS_IN_MATRIX
1498 && archive
->FindRect("_i_frames", i
, &bounds
) == B_OK
)
1499 AddItem(item
, bounds
);
1509 BMenu::_Show(bool selectFirstItem
, bool keyDown
)
1511 if (Window() != NULL
)
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());
1535 if (window
->Lock()) {
1536 bool addAborted
= false;
1538 addAborted
= _AddDynamicItems(keyDown
);
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
1570 _UpdateWindowViewSize(true);
1573 if (selectFirstItem
)
1574 _SelectItem(ItemAt(0), false);
1586 BMenuWindow
* window
= dynamic_cast<BMenuWindow
*>(Window());
1587 if (window
== NULL
|| !window
->Lock())
1590 if (fSelected
!= NULL
)
1594 window
->DetachMenu();
1595 // we don't want to be deleted when the window is removed
1597 #if USE_CACHED_MENUWINDOW
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;
1618 BMenu::_Track(int* action
, long start
)
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
;
1629 // we will use this for keyboard selection
1634 GetMouse(&location
, &buttons
);
1638 bool releasedOnce
= buttons
== 0;
1639 while (fState
!= MENU_STATE_CLOSED
) {
1640 if (_CustomTrackingWantsToQuit())
1646 BMenuWindow
* window
= static_cast<BMenuWindow
*>(Window());
1647 BPoint screenLocation
= ConvertToScreen(location
);
1648 if (window
->CheckForScrolling(screenLocation
)) {
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
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
) {
1683 fState
= MENU_STATE_CLOSED
;
1684 } else if (submenuAction
== MENU_STATE_KEY_LEAVE_SUBMENU
) {
1686 BMenuItem
* temp
= fSelected
;
1687 // close the submenu:
1689 // but reselect the item itself for user:
1690 _SelectItem(temp
, false);
1693 // cancel key-nav state
1694 fState
= MENU_STATE_TRACKING
;
1696 fState
= MENU_STATE_TRACKING
;
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
;
1708 } else if (fState
== MENU_STATE_KEY_LEAVE_SUBMENU
) {
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
)) {
1720 fState
= MENU_STATE_TRACKING
;
1723 if (fSuper
!= NULL
) {
1724 // Give supermenu the chance to continue tracking
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
1745 snooze(snoozeAmount
);
1748 GetMouse(&newLocation
, &newButtons
, true);
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
;
1763 _UpdateStateClose(item
, location
, buttons
);
1770 // keyboard Enter will set this
1771 if (fChosenItem
!= NULL
)
1773 else if (fSelected
== NULL
) {
1774 // needed to cover (rare) mouse/ESC combination
1778 if (fSelected
!= NULL
&& LockLooper()) {
1783 // delete the menu window recycled for all the child menus
1784 _DeleteMenuWindow();
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 // +-------+----+---------+
1814 // | +----|---------+
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
1825 // The rectangles are used to calculate if the cursor is in the actual
1826 // navigation area (see _UpdateStateOpenSelect()).
1828 if (fSelected
== NULL
)
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
,
1845 navAreaRectBelow
.Set(position
.x
+ NAV_AREA_THRESHOLD
,
1846 position
.y
, menuBounds
.right
,
1847 submenuBounds
.bottom
);
1849 navAreaRectAbove
.Set(menuBounds
.left
,
1850 submenuBounds
.top
, position
.x
- NAV_AREA_THRESHOLD
,
1852 navAreaRectBelow
.Set(menuBounds
.left
,
1853 position
.y
, position
.x
- NAV_AREA_THRESHOLD
,
1854 submenuBounds
.bottom
);
1857 navAreaRectAbove
= BRect();
1858 navAreaRectBelow
= BRect();
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
)
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;
1890 BRect menuBounds
= ConvertToScreen(Bounds());
1892 fSelected
->Submenu()->LockLooper();
1893 BRect submenuBounds
= fSelected
->Submenu()->ConvertToScreen(
1894 fSelected
->Submenu()->Bounds());
1895 fSelected
->Submenu()->UnlockLooper();
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
;
1905 xOffset
= navAreaRectAbove
.right
- position
.x
;
1909 if (inNavAreaRectAbove
) {
1910 float yOffset
= navAreaRectAbove
.bottom
- position
.y
;
1911 float ratio
= navAreaRectAbove
.Width() / navAreaRectAbove
.Height();
1913 inNavArea
= yOffset
<= xOffset
/ ratio
;
1915 float yOffset
= navAreaRectBelow
.bottom
- position
.y
;
1916 float ratio
= navAreaRectBelow
.Width() / navAreaRectBelow
.Height();
1918 inNavArea
= yOffset
>= (navAreaRectBelow
.Height() - xOffset
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
);
1931 _UpdateNavigationArea(position
, navAreaRectAbove
,
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
,
1952 if (fState
!= MENU_STATE_TRACKING
)
1953 fState
= MENU_STATE_TRACKING
;
1958 BMenu::_UpdateStateClose(BMenuItem
* item
, const BPoint
& where
,
1959 const uint32
& buttons
)
1961 if (fState
== MENU_STATE_CLOSED
)
1964 if (buttons
!= 0 && _IsStickyMode()) {
1966 if (item
!= fSelected
) {
1968 _SelectItem(item
, false);
1971 fState
= MENU_STATE_CLOSED
;
1973 _SetStickyMode(false);
1974 } else if (buttons
== 0 && !_IsStickyMode()) {
1975 if (fExtraRect
!= NULL
&& fExtraRect
->Contains(where
)) {
1976 _SetStickyMode(true);
1978 // Setting this to NULL will prevent this code
1979 // to be executed next time
1981 if (item
!= fSelected
) {
1983 _SelectItem(item
, false);
1986 fState
= MENU_STATE_CLOSED
;
1993 BMenu::_AddItem(BMenuItem
* item
, int32 index
)
1995 ASSERT(item
!= NULL
);
1996 if (index
< 0 || index
> fItems
.CountItems())
1999 if (item
->IsMarked())
2002 if (!fItems
.AddItem(item
, index
))
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
;
2013 item
->Install(window
);
2015 item
->SetSuper(this);
2021 BMenu::_RemoveItems(int32 index
, int32 count
, BMenuItem
* item
,
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.
2033 if (fItems
.RemoveItem(item
)) {
2034 if (item
== fSelected
&& window
!= NULL
)
2037 item
->SetSuper(NULL
);
2040 success
= invalidateLayout
= true;
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
));
2052 if (fItems
.RemoveItem(item
)) {
2053 if (item
== fSelected
&& window
!= NULL
)
2056 item
->SetSuper(NULL
);
2060 invalidateLayout
= true;
2062 // operation not entirely successful
2070 if (invalidateLayout
) {
2072 if (locked
&& window
!= NULL
) {
2074 _UpdateWindowViewSize(false);
2087 BMenu::_RelayoutIfNeeded()
2089 if (!fUseCachedMenuLayout
) {
2090 fUseCachedMenuLayout
= true;
2100 BMenu::_LayoutItems(int32 index
)
2106 _ComputeLayout(index
, fResizeToFit
, true, &width
, &height
);
2109 ResizeTo(width
, height
);
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
;
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();
2138 case B_ITEMS_IN_COLUMN
:
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
,
2160 case B_ITEMS_IN_ROW
:
2161 _ComputeRowLayout(index
, bestFit
, moveItems
, frame
);
2164 case B_ITEMS_IN_MATRIX
:
2165 _ComputeMatrixLayout(frame
);
2169 // change width depending on resize mode
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;
2179 size
.width
= Bounds().Width();
2181 size
.width
= frame
.Width();
2183 size
.height
= frame
.Height();
2186 *_width
= size
.width
;
2189 *_height
= size
.height
;
2192 fLayoutData
->preferred
= size
;
2195 fUseCachedMenuLayout
= true;
2200 BMenu::_ComputeColumnLayout(int32 index
, bool bestFit
, bool moveItems
,
2201 BRect
* overrideFrame
, BRect
& frame
)
2203 bool command
= false;
2204 bool control
= false;
2206 bool option
= false;
2209 frame
= ItemAt(index
- 1)->Frame();
2210 else if (overrideFrame
!= NULL
)
2211 frame
.Set(0, 0, overrideFrame
->right
, -1);
2213 frame
.Set(0, 0, 0, -1);
2218 for (; index
< fItems
.CountItems(); index
++) {
2219 BMenuItem
* item
= ItemAt(index
);
2223 item
->GetContentSize(&width
, &height
);
2225 if (item
->fModifiers
&& item
->fShortcutChar
) {
2226 width
+= font
.Size();
2227 if ((item
->fModifiers
& B_COMMAND_KEY
) != 0)
2230 if ((item
->fModifiers
& B_CONTROL_KEY
) != 0)
2233 if ((item
->fModifiers
& B_SHIFT_KEY
) != 0)
2236 if ((item
->fModifiers
& B_OPTION_KEY
) != 0)
2240 item
->fBounds
.left
= 0.0f
;
2241 item
->fBounds
.top
= frame
.bottom
+ 1.0f
;
2242 item
->fBounds
.bottom
= item
->fBounds
.top
+ height
+ fPad
.top
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
;
2254 += BPrivate::MenuPrivate::MenuItemCommand()->Bounds().Width() + 1;
2258 += BPrivate::MenuPrivate::MenuItemControl()->Bounds().Width() + 1;
2262 += BPrivate::MenuPrivate::MenuItemOption()->Bounds().Width() + 1;
2266 += BPrivate::MenuPrivate::MenuItemShift()->Bounds().Width() + 1;
2269 if (fMaxContentWidth
> 0)
2270 frame
.right
= std::min(frame
.right
, fMaxContentWidth
);
2273 for (int32 i
= 0; i
< fItems
.CountItems(); i
++)
2274 ItemAt(i
)->fBounds
.right
= frame
.right
;
2278 frame
.right
= ceilf(frame
.right
);
2283 BMenu::_ComputeRowLayout(int32 index
, bool bestFit
, bool moveItems
,
2288 frame
.Set(0.0f
, 0.0f
, 0.0f
, ceilf(fh
.ascent
+ fh
.descent
+ fPad
.top
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
2302 frame
.right
= item
->Frame().right
+ 1.0f
;
2303 frame
.bottom
= std::max(frame
.bottom
, height
+ fPad
.top
+ fPad
.bottom
);
2307 for (int32 i
= 0; i
< fItems
.CountItems(); i
++)
2308 ItemAt(i
)->fBounds
.bottom
= frame
.bottom
;
2312 frame
.right
= ceilf(frame
.right
);
2314 frame
.right
= Bounds().right
;
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
);
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
);
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())
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.");
2355 if (superMenu
->Layout() == B_ITEMS_IN_COLUMN
)
2356 point
= superItem
->Frame().RightTop() + BPoint(1.0f
, 1.0f
);
2358 point
= superItem
->Frame().LeftBottom() + BPoint(1.0f
, 1.0f
);
2360 superMenu
->ConvertToScreen(&point
);
2367 BMenu::_CalcFrame(BPoint where
, bool* scrollOn
)
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);
2401 frame
.OffsetBy(-frame
.left
+ 6, 0);
2403 if (frame
.bottom
> screenFrame
.bottom
)
2404 frame
.OffsetBy(0, screenFrame
.bottom
- frame
.bottom
);
2406 if (frame
.bottom
> screenFrame
.bottom
) {
2407 if (scrollOn
!= NULL
&& superMenu
!= NULL
2408 && dynamic_cast<BMenuBar
*>(superMenu
) != NULL
2409 && frame
.top
< (screenFrame
.bottom
- 80)) {
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);
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,
2426 scroll
= screenFrame
.Height() < frame
.Height();
2429 if (scrollOn
!= NULL
)
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
))
2449 BMenu::_State(BMenuItem
** item
) const
2451 if (fState
== MENU_STATE_TRACKING
|| fState
== MENU_STATE_CLOSED
)
2454 if (fSelected
!= NULL
&& fSelected
->Submenu() != NULL
)
2455 return fSelected
->Submenu()->_State(item
);
2462 BMenu::_InvokeItem(BMenuItem
* item
, bool now
)
2464 if (!item
->IsEnabled())
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()) {
2473 Window()->UpdateIfNeeded();
2475 item
->Select(false);
2476 Window()->UpdateIfNeeded();
2479 Window()->UpdateIfNeeded();
2481 item
->Select(false);
2482 Window()->UpdateIfNeeded();
2486 // Lock the root menu window before calling BMenuItem::Invoke()
2487 BMenu
* parent
= this;
2488 BMenu
* rootMenu
= NULL
;
2491 parent
= rootMenu
->Supermenu();
2492 } while (parent
!= NULL
);
2494 if (rootMenu
->LockLooper()) {
2496 rootMenu
->UnlockLooper();
2502 BMenu::_OverSuper(BPoint location
)
2507 return fSuperbounds
.Contains(location
);
2512 BMenu::_OverSubmenu(BMenuItem
* item
, BPoint loc
)
2517 BMenu
* subMenu
= item
->Submenu();
2518 if (subMenu
== NULL
|| subMenu
->Window() == NULL
)
2521 // assume that loc is in screen coordinates
2522 if (subMenu
->Window()->Frame().Contains(loc
))
2525 return subMenu
->_OverSubmenu(subMenu
->fSelected
, loc
);
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
);
2539 return fCachedMenuWindow
;
2544 BMenu::_DeleteMenuWindow()
2546 if (fCachedMenuWindow
!= NULL
) {
2547 fCachedMenuWindow
->Lock();
2548 fCachedMenuWindow
->Quit();
2549 fCachedMenuWindow
= NULL
;
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
))
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
) {
2578 BMenu::_Superbounds() const
2580 return fSuperbounds
;
2585 BMenu::_CacheFontInfo()
2589 fAscent
= fh
.ascent
;
2590 fDescent
= fh
.descent
;
2591 fFontHeight
= ceilf(fh
.ascent
+ fh
.descent
+ fh
.leading
);
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());
2611 BMenu::_Install(BWindow
* target
)
2613 for (int32 i
= 0; i
< CountItems(); i
++)
2614 ItemAt(i
)->Install(target
);
2621 for (int32 i
= 0; i
< CountItems(); i
++)
2622 ItemAt(i
)->Uninstall();
2627 BMenu::_SelectItem(BMenuItem
* item
, bool showSubmenu
, bool selectFirstItem
,
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
)
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);
2659 BMenu::_SelectNextItem(BMenuItem
* item
, bool forward
)
2661 if (CountItems() == 0) // cannot select next item in an empty menu
2664 BMenuItem
* nextItem
= _NextItem(item
, forward
);
2665 if (nextItem
== NULL
)
2668 _SelectItem(nextItem
, dynamic_cast<BMenuBar
*>(this) != NULL
);
2671 be_app
->ObscureCursor();
2680 BMenu::_NextItem(BMenuItem
* item
, bool forward
) const
2682 const int32 numItems
= fItems
.CountItems();
2686 int32 index
= fItems
.IndexOf(item
);
2687 int32 loopCount
= numItems
;
2688 while (--loopCount
) {
2689 // Cycle through menu items in the given direction...
2695 // ... wrap around...
2697 index
= numItems
- 1;
2698 else if (index
>= numItems
)
2701 // ... and return the first suitable item found.
2702 BMenuItem
* nextItem
= ItemAt(index
);
2703 if (nextItem
->IsEnabled())
2707 // If no other suitable item was found, return NULL.
2713 BMenu::_SetIgnoreHidden(bool on
)
2720 BMenu::_SetStickyMode(bool on
)
2722 if (fStickyMode
== on
)
2727 if (fSuper
!= NULL
) {
2728 // propagate the status to the super menu
2729 fSuper
->_SetStickyMode(on
);
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();
2745 BMenu::_IsStickyMode() const
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
2760 if (get_modifier_key(B_LEFT_SHIFT_KEY
, &value
) != B_OK
)
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
2774 if (get_modifier_key(B_LEFT_CONTROL_KEY
, &value
) != B_OK
)
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
2788 if (get_modifier_key(B_LEFT_COMMAND_KEY
, &value
) != B_OK
)
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
2802 if (get_modifier_key(B_LEFT_OPTION_KEY
, &value
) != B_OK
)
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
2816 if (get_modifier_key(B_MENU_KEY
, &value
) != B_OK
)
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();
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) {
2839 if (_ChooseTrigger(item
->Label(), index
, trigger
, triggerList
))
2840 item
->SetAutomaticTrigger(index
, trigger
);
2847 BMenu::_ChooseTrigger(const char* title
, int32
& index
, uint32
& trigger
,
2848 BPrivate::TriggerList
& triggers
)
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
)) {
2860 trigger
= tolower(c
);
2861 return triggers
.AddTrigger(c
);
2865 // then, if we still haven't found anything, we accept them all
2867 while ((c
= UTF8ToCharCode(&title
)) != 0) {
2868 if (!isspace(c
) && !triggers
.HasTrigger(c
)) {
2869 trigger
= tolower(c
);
2870 return triggers
.AddTrigger(c
);
2881 BMenu::_UpdateWindowViewSize(const bool &move
)
2883 BMenuWindow
* window
= static_cast<BMenuWindow
*>(Window());
2887 if (dynamic_cast<BMenuBar
*>(this) != NULL
)
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) {
2901 window
->ResizeTo(Bounds().Width(), Bounds().Height());
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());
2911 // Or, in case our parent was a BMenuBar enable scrolling with
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
);
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
);
2938 window
->ResizeTo(StringWidth(BPrivate::kEmptyMenuLabel
)
2939 + fPad
.left
+ fPad
.right
,
2940 fFontHeight
+ fPad
.top
+ fPad
.bottom
);
2944 window
->MoveTo(frame
.LeftTop());
2949 BMenu::_AddDynamicItems(bool keyDown
)
2951 bool addAborted
= false;
2952 if (AddDynamicItem(B_INITIAL_ADD
)) {
2953 BMenuItem
* superItem
= Superitem();
2954 BMenu
* superMenu
= Supermenu();
2956 if (superMenu
!= NULL
2957 && !superMenu
->_OkToProceed(superItem
, keyDown
)) {
2958 AddDynamicItem(B_ABORT
);
2962 } while (AddDynamicItem(B_PROCESSING
));
2970 BMenu::_OkToProceed(BMenuItem
* item
, bool keyDown
)
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
2982 if ((buttons
!= 0 && stickyMode
)
2983 || ((dynamic_cast<BMenuBar
*>(this) == NULL
2984 && (buttons
== 0 && !stickyMode
))
2985 || ((_HitTestItems(where
) != item
) && !keyDown
))) {
2994 BMenu::_CustomTrackingWantsToQuit()
2996 if (fExtraMenuData
!= NULL
&& fExtraMenuData
->trackingHook
!= NULL
2997 && fExtraMenuData
->trackingState
!= NULL
) {
2998 return fExtraMenuData
->trackingHook(this,
2999 fExtraMenuData
->trackingState
);
3007 BMenu::_QuitTracking(bool onlyThis
)
3010 if (BMenuBar
* menuBar
= dynamic_cast<BMenuBar
*>(this))
3011 menuBar
->_RestoreFocus();
3013 fState
= MENU_STATE_CLOSED
;
3016 // Close the whole menu hierarchy
3017 if (Supermenu() != NULL
)
3018 Supermenu()->fState
= MENU_STATE_CLOSED
;
3020 if (_IsStickyMode())
3021 _SetStickyMode(false);
3024 be_app
->ShowCursor();
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
3041 set_menu_info(menu_info
* info
)
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
3060 get_menu_info(menu_info
* info
)
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
);
3077 B_IF_GCC_2(InvalidateLayout__5BMenub
,_ZN5BMenu16InvalidateLayoutEb
)(
3078 BMenu
* menu
, bool descendants
)
3080 menu
->InvalidateLayout();