2 * Copyright 2001-2015 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(std::max(14.0f
, be_plain_font
->Size() + 2.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
),
254 fPad(14.0f
, 2.0f
, 20.0f
, 0.0f
),
256 fCachedMenuWindow(NULL
),
263 fLayout(B_ITEMS_IN_MATRIX
),
265 fMaxContentWidth(0.0f
),
266 fInitMatrixSize(NULL
),
267 fExtraMenuData(NULL
),
270 fUseCachedMenuLayout(false),
274 fTrackNewBounds(false),
277 fTriggerEnabled(true),
278 fRedrawAfterSticky(false),
279 fAttachAborted(false)
285 BMenu::BMenu(BMessage
* archive
)
289 fPad(14.0f
, 2.0f
, 20.0f
, 0.0f
),
291 fCachedMenuWindow(NULL
),
297 fState(MENU_STATE_CLOSED
),
298 fLayout(B_ITEMS_IN_ROW
),
300 fMaxContentWidth(0.0f
),
301 fInitMatrixSize(NULL
),
302 fExtraMenuData(NULL
),
305 fUseCachedMenuLayout(false),
309 fTrackNewBounds(false),
312 fTriggerEnabled(true),
313 fRedrawAfterSticky(false),
314 fAttachAborted(false)
324 RemoveItems(0, CountItems(), true);
326 delete fInitMatrixSize
;
327 delete fExtraMenuData
;
333 BMenu::Instantiate(BMessage
* archive
)
335 if (validate_instantiation(archive
, "BMenu"))
336 return new (nothrow
) BMenu(archive
);
343 BMenu::Archive(BMessage
* data
, bool deep
) const
345 status_t err
= BView::Archive(data
, deep
);
347 if (err
== B_OK
&& Layout() != B_ITEMS_IN_ROW
)
348 err
= data
->AddInt32("_layout", Layout());
350 err
= data
->AddBool("_rsize_to_fit", fResizeToFit
);
352 err
= data
->AddBool("_disable", !IsEnabled());
354 err
= data
->AddBool("_radio", IsRadioMode());
356 err
= data
->AddBool("_trig_disabled", AreTriggersEnabled());
358 err
= data
->AddBool("_dyn_label", fDynamicName
);
360 err
= data
->AddFloat("_maxwidth", fMaxContentWidth
);
361 if (err
== B_OK
&& deep
) {
362 BMenuItem
* item
= NULL
;
364 while ((item
= ItemAt(index
++)) != NULL
) {
366 item
->Archive(&itemData
, deep
);
367 err
= data
->AddMessage("_items", &itemData
);
370 if (fLayout
== B_ITEMS_IN_MATRIX
) {
371 err
= data
->AddRect("_i_frames", item
->fBounds
);
381 BMenu::AttachedToWindow()
383 BView::AttachedToWindow();
385 _GetShiftKey(sShiftKey
);
386 _GetControlKey(sControlKey
);
387 _GetCommandKey(sCommandKey
);
388 _GetOptionKey(sOptionKey
);
389 _GetMenuKey(sMenuKey
);
391 fAttachAborted
= _AddDynamicItems();
393 if (!fAttachAborted
) {
396 _UpdateWindowViewSize(false);
402 BMenu::DetachedFromWindow()
404 BView::DetachedFromWindow();
411 BView::AllAttached();
418 BView::AllDetached();
423 BMenu::Draw(BRect updateRect
)
425 if (_RelayoutIfNeeded()) {
430 DrawBackground(updateRect
);
431 DrawItems(updateRect
);
436 BMenu::MessageReceived(BMessage
* message
)
438 switch (message
->what
) {
439 case B_MOUSE_WHEEL_CHANGED
:
442 message
->FindFloat("be:wheel_delta_y", &deltaY
);
446 BMenuWindow
* window
= dynamic_cast<BMenuWindow
*>(Window());
452 window
->GetSteps(&smallStep
, &largeStep
);
454 // pressing the shift key scrolls faster
455 if ((modifiers() & B_SHIFT_KEY
) != 0)
460 window
->TryScrollBy(deltaY
);
465 BView::MessageReceived(message
);
472 BMenu::KeyDown(const char* bytes
, int32 numBytes
)
474 // TODO: Test how it works on BeOS R5 and implement this correctly
477 if (fLayout
== B_ITEMS_IN_COLUMN
)
478 _SelectNextItem(fSelected
, false);
483 BMenuBar
* bar
= dynamic_cast<BMenuBar
*>(Supermenu());
484 if (bar
!= NULL
&& fState
== MENU_STATE_CLOSED
) {
485 // tell MenuBar's _Track:
486 bar
->fState
= MENU_STATE_KEY_TO_SUBMENU
;
488 if (fLayout
== B_ITEMS_IN_COLUMN
)
489 _SelectNextItem(fSelected
, true);
494 if (fLayout
== B_ITEMS_IN_ROW
)
495 _SelectNextItem(fSelected
, false);
497 // this case has to be handled a bit specially.
498 BMenuItem
* item
= Superitem();
500 if (dynamic_cast<BMenuBar
*>(Supermenu())) {
501 // If we're at the top menu below the menu bar, pass
502 // the keypress to the menu bar so we can move to
503 // another top level menu.
504 BMessenger
messenger(Supermenu());
505 messenger
.SendMessage(Window()->CurrentMessage());
508 fState
= MENU_STATE_KEY_LEAVE_SUBMENU
;
515 if (fLayout
== B_ITEMS_IN_ROW
)
516 _SelectNextItem(fSelected
, true);
518 if (fSelected
!= NULL
&& fSelected
->Submenu() != NULL
) {
519 fSelected
->Submenu()->_SetStickyMode(true);
520 // fix me: this shouldn't be needed but dynamic menus
521 // aren't getting it set correctly when keyboard
522 // navigating, which aborts the attach
523 fState
= MENU_STATE_KEY_TO_SUBMENU
;
524 _SelectItem(fSelected
, true, true, true);
525 } else if (dynamic_cast<BMenuBar
*>(Supermenu())) {
526 // if we have no submenu and we're an
527 // item in the top menu below the menubar,
528 // pass the keypress to the menubar
529 // so you can use the keypress to switch menus.
530 BMessenger
messenger(Supermenu());
531 messenger
.SendMessage(Window()->CurrentMessage());
539 BMenuWindow
* window
= dynamic_cast<BMenuWindow
*>(Window());
540 if (window
== NULL
|| !window
->HasScrollers())
543 int32 deltaY
= bytes
[0] == B_PAGE_UP
? -1 : 1;
546 window
->GetSteps(NULL
, &largeStep
);
547 window
->TryScrollBy(deltaY
* largeStep
);
553 if (fSelected
!= NULL
) {
554 fChosenItem
= fSelected
;
555 // preserve for exit handling
556 _QuitTracking(false);
562 if (fState
== MENU_STATE_CLOSED
563 && dynamic_cast<BMenuBar
*>(Supermenu())) {
564 // Keyboard may show menu without tracking it
565 BMessenger
messenger(Supermenu());
566 messenger
.SendMessage(Window()->CurrentMessage());
568 _QuitTracking(false);
573 uint32 trigger
= UTF8ToCharCode(&bytes
);
575 for (uint32 i
= CountItems(); i
-- > 0;) {
576 BMenuItem
* item
= ItemAt(i
);
577 if (item
->fTriggerIndex
< 0 || item
->fTrigger
!= trigger
)
581 _QuitTracking(false);
593 _ValidatePreferredSize();
595 BSize size
= (GetLayout() != NULL
? GetLayout()->MinSize()
596 : fLayoutData
->preferred
);
598 return BLayoutUtils::ComposeSize(ExplicitMinSize(), size
);
605 _ValidatePreferredSize();
607 BSize size
= (GetLayout() != NULL
? GetLayout()->MaxSize()
608 : fLayoutData
->preferred
);
610 return BLayoutUtils::ComposeSize(ExplicitMaxSize(), size
);
615 BMenu::PreferredSize()
617 _ValidatePreferredSize();
619 BSize size
= (GetLayout() != NULL
? GetLayout()->PreferredSize()
620 : fLayoutData
->preferred
);
622 return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), size
);
627 BMenu::GetPreferredSize(float* _width
, float* _height
)
629 _ValidatePreferredSize();
632 *_width
= fLayoutData
->preferred
.width
;
635 *_height
= fLayoutData
->preferred
.height
;
640 BMenu::ResizeToPreferred()
642 BView::ResizeToPreferred();
649 // If the user set a layout, we let the base class version call its
651 if (GetLayout() != NULL
) {
656 if (_RelayoutIfNeeded())
662 BMenu::FrameMoved(BPoint new_position
)
664 BView::FrameMoved(new_position
);
669 BMenu::FrameResized(float new_width
, float new_height
)
671 BView::FrameResized(new_width
, new_height
);
676 BMenu::InvalidateLayout()
678 fUseCachedMenuLayout
= false;
679 // This method exits for backwards compatibility reasons, it is used to
680 // invalidate the menu layout, but we also use call
681 // BView::InvalidateLayout() for good measure. Don't delete this method!
682 BView::InvalidateLayout(false);
687 BMenu::MakeFocus(bool focused
)
689 BView::MakeFocus(focused
);
694 BMenu::AddItem(BMenuItem
* item
)
696 return AddItem(item
, CountItems());
701 BMenu::AddItem(BMenuItem
* item
, int32 index
)
703 if (fLayout
== B_ITEMS_IN_MATRIX
) {
704 debugger("BMenu::AddItem(BMenuItem*, int32) this method can only "
705 "be called if the menu layout is not B_ITEMS_IN_MATRIX");
708 if (item
== NULL
|| !_AddItem(item
, index
))
713 if (!Window()->IsHidden()) {
715 _UpdateWindowViewSize(false);
726 BMenu::AddItem(BMenuItem
* item
, BRect frame
)
728 if (fLayout
!= B_ITEMS_IN_MATRIX
) {
729 debugger("BMenu::AddItem(BMenuItem*, BRect) this method can only "
730 "be called if the menu layout is B_ITEMS_IN_MATRIX");
736 item
->fBounds
= frame
;
738 int32 index
= CountItems();
739 if (!_AddItem(item
, index
))
743 if (!Window()->IsHidden()) {
755 BMenu::AddItem(BMenu
* submenu
)
757 BMenuItem
* item
= new (nothrow
) BMenuItem(submenu
);
761 if (!AddItem(item
, CountItems())) {
762 item
->fSubmenu
= NULL
;
772 BMenu::AddItem(BMenu
* submenu
, int32 index
)
774 if (fLayout
== B_ITEMS_IN_MATRIX
) {
775 debugger("BMenu::AddItem(BMenuItem*, int32) this method can only "
776 "be called if the menu layout is not B_ITEMS_IN_MATRIX");
779 BMenuItem
* item
= new (nothrow
) BMenuItem(submenu
);
783 if (!AddItem(item
, index
)) {
784 item
->fSubmenu
= NULL
;
794 BMenu::AddItem(BMenu
* submenu
, BRect frame
)
796 if (fLayout
!= B_ITEMS_IN_MATRIX
) {
797 debugger("BMenu::AddItem(BMenu*, BRect) this method can only "
798 "be called if the menu layout is B_ITEMS_IN_MATRIX");
801 BMenuItem
* item
= new (nothrow
) BMenuItem(submenu
);
805 if (!AddItem(item
, frame
)) {
806 item
->fSubmenu
= NULL
;
816 BMenu::AddList(BList
* list
, int32 index
)
818 // TODO: test this function, it's not documented in the bebook.
822 bool locked
= LockLooper();
824 int32 numItems
= list
->CountItems();
825 for (int32 i
= 0; i
< numItems
; i
++) {
826 BMenuItem
* item
= static_cast<BMenuItem
*>(list
->ItemAt(i
));
828 if (!_AddItem(item
, index
+ i
))
834 if (locked
&& Window() != NULL
&& !Window()->IsHidden()) {
835 // Make sure we update the layout if needed.
837 _UpdateWindowViewSize(false);
849 BMenu::AddSeparatorItem()
851 BMenuItem
* item
= new (nothrow
) BSeparatorItem();
852 if (!item
|| !AddItem(item
, CountItems())) {
862 BMenu::RemoveItem(BMenuItem
* item
)
864 return _RemoveItems(0, 0, item
, false);
869 BMenu::RemoveItem(int32 index
)
871 BMenuItem
* item
= ItemAt(index
);
873 _RemoveItems(0, 0, item
, false);
879 BMenu::RemoveItems(int32 index
, int32 count
, bool deleteItems
)
881 return _RemoveItems(index
, count
, NULL
, deleteItems
);
886 BMenu::RemoveItem(BMenu
* submenu
)
888 for (int32 i
= 0; i
< fItems
.CountItems(); i
++) {
889 if (static_cast<BMenuItem
*>(fItems
.ItemAtFast(i
))->Submenu()
891 return _RemoveItems(i
, 1, NULL
, false);
900 BMenu::CountItems() const
902 return fItems
.CountItems();
907 BMenu::ItemAt(int32 index
) const
909 return static_cast<BMenuItem
*>(fItems
.ItemAt(index
));
914 BMenu::SubmenuAt(int32 index
) const
916 BMenuItem
* item
= static_cast<BMenuItem
*>(fItems
.ItemAt(index
));
917 return item
!= NULL
? item
->Submenu() : NULL
;
922 BMenu::IndexOf(BMenuItem
* item
) const
924 return fItems
.IndexOf(item
);
929 BMenu::IndexOf(BMenu
* submenu
) const
931 for (int32 i
= 0; i
< fItems
.CountItems(); i
++) {
932 if (ItemAt(i
)->Submenu() == submenu
)
941 BMenu::FindItem(const char* label
) const
943 BMenuItem
* item
= NULL
;
945 for (int32 i
= 0; i
< CountItems(); i
++) {
948 if (item
->Label() && strcmp(item
->Label(), label
) == 0)
951 if (item
->Submenu() != NULL
) {
952 item
= item
->Submenu()->FindItem(label
);
963 BMenu::FindItem(uint32 command
) const
965 BMenuItem
* item
= NULL
;
967 for (int32 i
= 0; i
< CountItems(); i
++) {
970 if (item
->Command() == command
)
973 if (item
->Submenu() != NULL
) {
974 item
= item
->Submenu()->FindItem(command
);
985 BMenu::SetTargetForItems(BHandler
* handler
)
987 status_t status
= B_OK
;
988 for (int32 i
= 0; i
< fItems
.CountItems(); i
++) {
989 status
= ItemAt(i
)->SetTarget(handler
);
999 BMenu::SetTargetForItems(BMessenger messenger
)
1001 status_t status
= B_OK
;
1002 for (int32 i
= 0; i
< fItems
.CountItems(); i
++) {
1003 status
= ItemAt(i
)->SetTarget(messenger
);
1013 BMenu::SetEnabled(bool enable
)
1015 if (fEnabled
== enable
)
1020 if (dynamic_cast<_BMCMenuBar_
*>(Supermenu()) != NULL
)
1021 Supermenu()->SetEnabled(enable
);
1024 fSuperitem
->SetEnabled(enable
);
1029 BMenu::SetRadioMode(bool on
)
1033 SetLabelFromMarked(false);
1038 BMenu::SetTriggersEnabled(bool enable
)
1040 fTriggerEnabled
= enable
;
1045 BMenu::SetMaxContentWidth(float width
)
1047 fMaxContentWidth
= width
;
1052 BMenu::SetLabelFromMarked(bool on
)
1061 BMenu::IsLabelFromMarked()
1063 return fDynamicName
;
1068 BMenu::IsEnabled() const
1073 return fSuper
? fSuper
->IsEnabled() : true ;
1078 BMenu::IsRadioMode() const
1085 BMenu::AreTriggersEnabled() const
1087 return fTriggerEnabled
;
1092 BMenu::IsRedrawAfterSticky() const
1094 return fRedrawAfterSticky
;
1099 BMenu::MaxContentWidth() const
1101 return fMaxContentWidth
;
1108 for (int32 i
= 0; i
< fItems
.CountItems(); i
++) {
1109 BMenuItem
* item
= ItemAt(i
);
1111 if (item
->IsMarked())
1120 BMenu::FindMarkedIndex()
1122 for (int32 i
= 0; i
< fItems
.CountItems(); i
++) {
1123 BMenuItem
* item
= ItemAt(i
);
1125 if (item
->IsMarked())
1134 BMenu::Supermenu() const
1141 BMenu::Superitem() const
1148 BMenu::ResolveSpecifier(BMessage
* msg
, int32 index
, BMessage
* specifier
,
1149 int32 form
, const char* property
)
1151 BPropertyInfo
propInfo(sPropList
);
1152 BHandler
* target
= NULL
;
1154 switch (propInfo
.FindMatch(msg
, 0, specifier
, form
, property
)) {
1169 // TODO: redirect to menu
1179 // TODO: redirect to menuitem
1185 target
= BView::ResolveSpecifier(msg
, index
, specifier
, form
,
1193 BMenu::GetSupportedSuites(BMessage
* data
)
1198 status_t err
= data
->AddString("suites", "suite/vnd.Be-menu");
1203 BPropertyInfo
propertyInfo(sPropList
);
1204 err
= data
->AddFlat("messages", &propertyInfo
);
1209 return BView::GetSupportedSuites(data
);
1214 BMenu::Perform(perform_code code
, void* _data
)
1217 case PERFORM_CODE_MIN_SIZE
:
1218 ((perform_data_min_size
*)_data
)->return_value
1222 case PERFORM_CODE_MAX_SIZE
:
1223 ((perform_data_max_size
*)_data
)->return_value
1227 case PERFORM_CODE_PREFERRED_SIZE
:
1228 ((perform_data_preferred_size
*)_data
)->return_value
1229 = BMenu::PreferredSize();
1232 case PERFORM_CODE_LAYOUT_ALIGNMENT
:
1233 ((perform_data_layout_alignment
*)_data
)->return_value
1234 = BMenu::LayoutAlignment();
1237 case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH
:
1238 ((perform_data_has_height_for_width
*)_data
)->return_value
1239 = BMenu::HasHeightForWidth();
1242 case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH
:
1244 perform_data_get_height_for_width
* data
1245 = (perform_data_get_height_for_width
*)_data
;
1246 BMenu::GetHeightForWidth(data
->width
, &data
->min
, &data
->max
,
1251 case PERFORM_CODE_SET_LAYOUT
:
1253 perform_data_set_layout
* data
= (perform_data_set_layout
*)_data
;
1254 BMenu::SetLayout(data
->layout
);
1258 case PERFORM_CODE_LAYOUT_INVALIDATED
:
1260 perform_data_layout_invalidated
* data
1261 = (perform_data_layout_invalidated
*)_data
;
1262 BMenu::LayoutInvalidated(data
->descendants
);
1266 case PERFORM_CODE_DO_LAYOUT
:
1273 return BView::Perform(code
, _data
);
1277 // #pragma mark - BMenu protected methods
1280 BMenu::BMenu(BRect frame
, const char* name
, uint32 resizingMode
, uint32 flags
,
1281 menu_layout layout
, bool resizeToFit
)
1283 BView(frame
, name
, resizingMode
, flags
),
1286 fCachedMenuWindow(NULL
),
1292 fState(MENU_STATE_CLOSED
),
1295 fMaxContentWidth(0.0f
),
1296 fInitMatrixSize(NULL
),
1297 fExtraMenuData(NULL
),
1299 fResizeToFit(resizeToFit
),
1300 fUseCachedMenuLayout(false),
1302 fDynamicName(false),
1304 fTrackNewBounds(false),
1306 fIgnoreHidden(true),
1307 fTriggerEnabled(true),
1308 fRedrawAfterSticky(false),
1309 fAttachAborted(false)
1316 BMenu::SetItemMargins(float left
, float top
, float right
, float bottom
)
1318 fPad
.Set(left
, top
, right
, bottom
);
1323 BMenu::GetItemMargins(float* _left
, float* _top
, float* _right
,
1324 float* _bottom
) const
1333 *_right
= fPad
.right
;
1335 if (_bottom
!= NULL
)
1336 *_bottom
= fPad
.bottom
;
1341 BMenu::Layout() const
1355 BMenu::Show(bool selectFirst
)
1371 BMenu::Track(bool sticky
, BRect
* clickToOpenRect
)
1373 if (sticky
&& LockLooper()) {
1374 //RedrawAfterSticky(Bounds());
1375 // the call above didn't do anything, so I've removed it for now
1379 if (clickToOpenRect
!= NULL
&& LockLooper()) {
1380 fExtraRect
= clickToOpenRect
;
1381 ConvertFromScreen(fExtraRect
);
1385 _SetStickyMode(sticky
);
1388 BMenuItem
* menuItem
= _Track(&action
);
1396 // #pragma mark - BMenu private methods
1400 BMenu::AddDynamicItem(add_state state
)
1402 // Implemented in subclasses
1408 BMenu::DrawBackground(BRect updateRect
)
1410 rgb_color base
= ui_color(B_MENU_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 BMenu::SetTrackingHook(menu_tracking_hook func
, void* state
)
1439 delete fExtraMenuData
;
1440 fExtraMenuData
= new (nothrow
) BPrivate::ExtraMenuData(func
, state
);
1444 void BMenu::_ReservedMenu3() {}
1445 void BMenu::_ReservedMenu4() {}
1446 void BMenu::_ReservedMenu5() {}
1447 void BMenu::_ReservedMenu6() {}
1451 BMenu::_InitData(BMessage
* archive
)
1453 BPrivate::kEmptyMenuLabel
= B_TRANSLATE("<empty>");
1455 // TODO: Get _color, _fname, _fflt from the message, if present
1457 font
.SetFamilyAndStyle(sMenuInfo
.f_family
, sMenuInfo
.f_style
);
1458 font
.SetSize(sMenuInfo
.font_size
);
1459 SetFont(&font
, B_FONT_FAMILY_AND_STYLE
| B_FONT_SIZE
);
1461 fLayoutData
= new LayoutData
;
1462 fLayoutData
->lastResizingMode
= ResizingMode();
1464 SetLowUIColor(B_MENU_BACKGROUND_COLOR
);
1465 SetViewColor(B_TRANSPARENT_COLOR
);
1467 fTriggerEnabled
= sMenuInfo
.triggers_always_shown
;
1469 if (archive
!= NULL
) {
1470 archive
->FindInt32("_layout", (int32
*)&fLayout
);
1471 archive
->FindBool("_rsize_to_fit", &fResizeToFit
);
1473 if (archive
->FindBool("_disable", &disabled
) == B_OK
)
1474 fEnabled
= !disabled
;
1475 archive
->FindBool("_radio", &fRadioMode
);
1477 bool disableTrigger
= false;
1478 archive
->FindBool("_trig_disabled", &disableTrigger
);
1479 fTriggerEnabled
= !disableTrigger
;
1481 archive
->FindBool("_dyn_label", &fDynamicName
);
1482 archive
->FindFloat("_maxwidth", &fMaxContentWidth
);
1485 for (int32 i
= 0; archive
->FindMessage("_items", i
, &msg
) == B_OK
; i
++) {
1486 BArchivable
* object
= instantiate_object(&msg
);
1487 if (BMenuItem
* item
= dynamic_cast<BMenuItem
*>(object
)) {
1489 if (fLayout
== B_ITEMS_IN_MATRIX
1490 && archive
->FindRect("_i_frames", i
, &bounds
) == B_OK
)
1491 AddItem(item
, bounds
);
1501 BMenu::_Show(bool selectFirstItem
, bool keyDown
)
1503 if (Window() != NULL
)
1506 // See if the supermenu has a cached menuwindow,
1507 // and use that one if possible.
1508 BMenuWindow
* window
= NULL
;
1509 bool ourWindow
= false;
1510 if (fSuper
!= NULL
) {
1511 fSuperbounds
= fSuper
->ConvertToScreen(fSuper
->Bounds());
1512 window
= fSuper
->_MenuWindow();
1515 // Otherwise, create a new one
1516 // This happens for "stand alone" BPopUpMenus
1517 // (i.e. not within a BMenuField)
1518 if (window
== NULL
) {
1519 // Menu windows get the BMenu's handler name
1520 window
= new (nothrow
) BMenuWindow(Name());
1527 if (window
->Lock()) {
1528 bool addAborted
= false;
1530 addAborted
= _AddDynamicItems(keyDown
);
1539 fAttachAborted
= false;
1541 window
->AttachMenu(this);
1543 if (ItemAt(0) != NULL
) {
1544 float width
, height
;
1545 ItemAt(0)->GetContentSize(&width
, &height
);
1547 window
->SetSmallStep(ceilf(height
));
1550 // Menu didn't have the time to add its items: aborting...
1551 if (fAttachAborted
) {
1552 window
->DetachMenu();
1553 // TODO: Probably not needed, we can just let _hide() quit the
1562 _UpdateWindowViewSize(true);
1565 if (selectFirstItem
)
1566 _SelectItem(ItemAt(0), false);
1578 BMenuWindow
* window
= dynamic_cast<BMenuWindow
*>(Window());
1579 if (window
== NULL
|| !window
->Lock())
1582 if (fSelected
!= NULL
)
1586 window
->DetachMenu();
1587 // we don't want to be deleted when the window is removed
1589 #if USE_CACHED_MENUWINDOW
1595 // it's our window, quit it
1597 _DeleteMenuWindow();
1598 // Delete the menu window used by our submenus
1602 // #pragma mark - mouse tracking
1605 const static bigtime_t kOpenSubmenuDelay
= 225000;
1606 const static bigtime_t kNavigationAreaTimeout
= 1000000;
1610 BMenu::_Track(int* action
, long start
)
1613 BMenuItem
* item
= NULL
;
1614 BRect navAreaRectAbove
;
1615 BRect navAreaRectBelow
;
1616 bigtime_t selectedTime
= system_time();
1617 bigtime_t navigationAreaTime
= 0;
1619 fState
= MENU_STATE_TRACKING
;
1621 // we will use this for keyboard selection
1626 GetMouse(&location
, &buttons
);
1630 bool releasedOnce
= buttons
== 0;
1631 while (fState
!= MENU_STATE_CLOSED
) {
1632 if (_CustomTrackingWantsToQuit())
1638 BMenuWindow
* window
= static_cast<BMenuWindow
*>(Window());
1639 BPoint screenLocation
= ConvertToScreen(location
);
1640 if (window
->CheckForScrolling(screenLocation
)) {
1645 // The order of the checks is important
1646 // to be able to handle overlapping menus:
1647 // first we check if mouse is inside a submenu,
1648 // then if the mouse is inside this menu,
1649 // then if it's over a super menu.
1650 if (_OverSubmenu(fSelected
, screenLocation
)
1651 || fState
== MENU_STATE_KEY_TO_SUBMENU
) {
1652 if (fState
== MENU_STATE_TRACKING
) {
1653 // not if from R.Arrow
1654 fState
= MENU_STATE_TRACKING_SUBMENU
;
1656 navAreaRectAbove
= BRect();
1657 navAreaRectBelow
= BRect();
1659 // Since the submenu has its own looper,
1660 // we can unlock ours. Doing so also make sure
1661 // that our window gets any update message to
1664 int submenuAction
= MENU_STATE_TRACKING
;
1665 BMenu
* submenu
= fSelected
->Submenu();
1666 submenu
->_SetStickyMode(_IsStickyMode());
1668 // The following call blocks until the submenu
1669 // gives control back to us, either because the mouse
1670 // pointer goes out of the submenu's bounds, or because
1671 // the user closes the menu
1672 BMenuItem
* submenuItem
= submenu
->_Track(&submenuAction
);
1673 if (submenuAction
== MENU_STATE_CLOSED
) {
1675 fState
= MENU_STATE_CLOSED
;
1676 } else if (submenuAction
== MENU_STATE_KEY_LEAVE_SUBMENU
) {
1678 BMenuItem
* temp
= fSelected
;
1679 // close the submenu:
1681 // but reselect the item itself for user:
1682 _SelectItem(temp
, false);
1685 // cancel key-nav state
1686 fState
= MENU_STATE_TRACKING
;
1688 fState
= MENU_STATE_TRACKING
;
1691 } else if ((item
= _HitTestItems(location
, B_ORIGIN
)) != NULL
) {
1692 _UpdateStateOpenSelect(item
, location
, navAreaRectAbove
,
1693 navAreaRectBelow
, selectedTime
, navigationAreaTime
);
1694 releasedOnce
= true;
1695 } else if (_OverSuper(screenLocation
)
1696 && fSuper
->fState
!= MENU_STATE_KEY_TO_SUBMENU
) {
1697 fState
= MENU_STATE_TRACKING
;
1700 } else if (fState
== MENU_STATE_KEY_LEAVE_SUBMENU
) {
1703 } else if (fSuper
== NULL
1704 || fSuper
->fState
!= MENU_STATE_KEY_TO_SUBMENU
) {
1705 // Mouse pointer outside menu:
1706 // If there's no other submenu opened,
1707 // deselect the current selected item
1708 if (fSelected
!= NULL
1709 && (fSelected
->Submenu() == NULL
1710 || fSelected
->Submenu()->Window() == NULL
)) {
1712 fState
= MENU_STATE_TRACKING
;
1715 if (fSuper
!= NULL
) {
1716 // Give supermenu the chance to continue tracking
1726 _UpdateStateClose(item
, location
, buttons
);
1728 if (fState
!= MENU_STATE_CLOSED
) {
1729 bigtime_t snoozeAmount
= 50000;
1731 BPoint newLocation
= location
;
1732 uint32 newButtons
= buttons
;
1734 // If user doesn't move the mouse, loop here,
1735 // so we don't interfere with keyboard menu navigation
1737 snooze(snoozeAmount
);
1740 GetMouse(&newLocation
, &newButtons
, true);
1742 } while (newLocation
== location
&& newButtons
== buttons
1743 && !(item
!= NULL
&& item
->Submenu() != NULL
1744 && item
->Submenu()->Window() == NULL
)
1745 && fState
== MENU_STATE_TRACKING
);
1747 if (newLocation
!= location
|| newButtons
!= buttons
) {
1748 if (!releasedOnce
&& newButtons
== 0 && buttons
!= 0)
1749 releasedOnce
= true;
1750 location
= newLocation
;
1751 buttons
= newButtons
;
1755 _UpdateStateClose(item
, location
, buttons
);
1762 // keyboard Enter will set this
1763 if (fChosenItem
!= NULL
)
1765 else if (fSelected
== NULL
) {
1766 // needed to cover (rare) mouse/ESC combination
1770 if (fSelected
!= NULL
&& LockLooper()) {
1775 // delete the menu window recycled for all the child menus
1776 _DeleteMenuWindow();
1783 BMenu::_UpdateNavigationArea(BPoint position
, BRect
& navAreaRectAbove
,
1784 BRect
& navAreaRectBelow
)
1786 #define NAV_AREA_THRESHOLD 8
1788 // The navigation area is a region in which mouse-overs won't select
1789 // the item under the cursor. This makes it easier to navigate to
1790 // submenus, as the cursor can be moved to submenu items directly instead
1791 // of having to move it horizontally into the submenu first. The concept
1792 // is illustrated below:
1794 // +-------+----+---------+
1806 // | +----|---------+
1810 // [1] Selected item, cursor position ('position')
1811 // [2] Upper navigation area rectangle ('navAreaRectAbove')
1812 // [3] Lower navigation area rectangle ('navAreaRectBelow')
1813 // [4] Upper navigation area
1814 // [5] Lower navigation area
1817 // The rectangles are used to calculate if the cursor is in the actual
1818 // navigation area (see _UpdateStateOpenSelect()).
1820 if (fSelected
== NULL
)
1823 BMenu
* submenu
= fSelected
->Submenu();
1825 if (submenu
!= NULL
) {
1826 BRect menuBounds
= ConvertToScreen(Bounds());
1828 BRect submenuBounds
;
1829 if (fSelected
->Submenu()->LockLooper()) {
1830 submenuBounds
= fSelected
->Submenu()->ConvertToScreen(
1831 fSelected
->Submenu()->Bounds());
1832 fSelected
->Submenu()->UnlockLooper();
1835 if (menuBounds
.left
< submenuBounds
.left
) {
1836 navAreaRectAbove
.Set(position
.x
+ NAV_AREA_THRESHOLD
,
1837 submenuBounds
.top
, menuBounds
.right
,
1839 navAreaRectBelow
.Set(position
.x
+ NAV_AREA_THRESHOLD
,
1840 position
.y
, menuBounds
.right
,
1841 submenuBounds
.bottom
);
1843 navAreaRectAbove
.Set(menuBounds
.left
,
1844 submenuBounds
.top
, position
.x
- NAV_AREA_THRESHOLD
,
1846 navAreaRectBelow
.Set(menuBounds
.left
,
1847 position
.y
, position
.x
- NAV_AREA_THRESHOLD
,
1848 submenuBounds
.bottom
);
1851 navAreaRectAbove
= BRect();
1852 navAreaRectBelow
= BRect();
1858 BMenu::_UpdateStateOpenSelect(BMenuItem
* item
, BPoint position
,
1859 BRect
& navAreaRectAbove
, BRect
& navAreaRectBelow
, bigtime_t
& selectedTime
,
1860 bigtime_t
& navigationAreaTime
)
1862 if (fState
== MENU_STATE_CLOSED
)
1865 if (item
!= fSelected
) {
1866 if (navigationAreaTime
== 0)
1867 navigationAreaTime
= system_time();
1869 position
= ConvertToScreen(position
);
1871 bool inNavAreaRectAbove
= navAreaRectAbove
.Contains(position
);
1872 bool inNavAreaRectBelow
= navAreaRectBelow
.Contains(position
);
1874 if (fSelected
== NULL
1875 || (!inNavAreaRectAbove
&& !inNavAreaRectBelow
)) {
1876 _SelectItem(item
, false);
1877 navAreaRectAbove
= BRect();
1878 navAreaRectBelow
= BRect();
1879 selectedTime
= system_time();
1880 navigationAreaTime
= 0;
1884 BRect menuBounds
= ConvertToScreen(Bounds());
1886 BRect submenuBounds
;
1887 if (fSelected
->Submenu()->LockLooper()) {
1888 fSelected
->Submenu()->ConvertToScreen(
1889 fSelected
->Submenu()->Bounds());
1890 fSelected
->Submenu()->UnlockLooper();
1895 // navAreaRectAbove and navAreaRectBelow have the same X
1896 // position and width, so it doesn't matter which one we use to
1897 // calculate the X offset
1898 if (menuBounds
.left
< submenuBounds
.left
)
1899 xOffset
= position
.x
- navAreaRectAbove
.left
;
1901 xOffset
= navAreaRectAbove
.right
- position
.x
;
1905 if (inNavAreaRectAbove
) {
1906 float yOffset
= navAreaRectAbove
.bottom
- position
.y
;
1907 float ratio
= navAreaRectAbove
.Width() / navAreaRectAbove
.Height();
1909 inNavArea
= yOffset
<= xOffset
/ ratio
;
1911 float yOffset
= navAreaRectBelow
.bottom
- position
.y
;
1912 float ratio
= navAreaRectBelow
.Width() / navAreaRectBelow
.Height();
1914 inNavArea
= yOffset
>= (navAreaRectBelow
.Height() - xOffset
1918 bigtime_t systime
= system_time();
1920 if (!inNavArea
|| (navigationAreaTime
> 0 && systime
-
1921 navigationAreaTime
> kNavigationAreaTimeout
)) {
1922 // Don't delay opening of submenu if the user had
1923 // to wait for the navigation area timeout anyway
1924 _SelectItem(item
, inNavArea
);
1927 _UpdateNavigationArea(position
, navAreaRectAbove
,
1930 navAreaRectAbove
= BRect();
1931 navAreaRectBelow
= BRect();
1934 selectedTime
= system_time();
1935 navigationAreaTime
= 0;
1937 } else if (fSelected
->Submenu() != NULL
&&
1938 system_time() - selectedTime
> kOpenSubmenuDelay
) {
1939 _SelectItem(fSelected
, true);
1941 if (!navAreaRectAbove
.IsValid() && !navAreaRectBelow
.IsValid()) {
1942 position
= ConvertToScreen(position
);
1943 _UpdateNavigationArea(position
, navAreaRectAbove
,
1948 if (fState
!= MENU_STATE_TRACKING
)
1949 fState
= MENU_STATE_TRACKING
;
1954 BMenu::_UpdateStateClose(BMenuItem
* item
, const BPoint
& where
,
1955 const uint32
& buttons
)
1957 if (fState
== MENU_STATE_CLOSED
)
1960 if (buttons
!= 0 && _IsStickyMode()) {
1962 if (item
!= fSelected
&& LockLooper()) {
1963 _SelectItem(item
, false);
1966 fState
= MENU_STATE_CLOSED
;
1968 _SetStickyMode(false);
1969 } else if (buttons
== 0 && !_IsStickyMode()) {
1970 if (fExtraRect
!= NULL
&& fExtraRect
->Contains(where
)) {
1971 _SetStickyMode(true);
1973 // Setting this to NULL will prevent this code
1974 // to be executed next time
1976 if (item
!= fSelected
&& LockLooper()) {
1977 _SelectItem(item
, false);
1980 fState
= MENU_STATE_CLOSED
;
1987 BMenu::_AddItem(BMenuItem
* item
, int32 index
)
1989 ASSERT(item
!= NULL
);
1990 if (index
< 0 || index
> fItems
.CountItems())
1993 if (item
->IsMarked())
1996 if (!fItems
.AddItem(item
, index
))
1999 // install the item on the supermenu's window
2000 // or onto our window, if we are a root menu
2001 BWindow
* window
= NULL
;
2002 if (Superitem() != NULL
)
2003 window
= Superitem()->fWindow
;
2007 item
->Install(window
);
2009 item
->SetSuper(this);
2015 BMenu::_RemoveItems(int32 index
, int32 count
, BMenuItem
* item
,
2018 bool success
= false;
2019 bool invalidateLayout
= false;
2021 bool locked
= LockLooper();
2022 BWindow
* window
= Window();
2024 // The plan is simple: If we're given a BMenuItem directly, we use it
2025 // and ignore index and count. Otherwise, we use them instead.
2027 if (fItems
.RemoveItem(item
)) {
2028 if (item
== fSelected
&& window
!= NULL
)
2031 item
->SetSuper(NULL
);
2034 success
= invalidateLayout
= true;
2037 // We iterate backwards because it's simpler
2038 int32 i
= std::min(index
+ count
- 1, fItems
.CountItems() - 1);
2039 // NOTE: the range check for "index" is done after
2040 // calculating the last index to be removed, so
2041 // that the range is not "shifted" unintentionally
2042 index
= std::max((int32
)0, index
);
2043 for (; i
>= index
; i
--) {
2044 item
= static_cast<BMenuItem
*>(fItems
.ItemAt(i
));
2046 if (fItems
.RemoveItem(item
)) {
2047 if (item
== fSelected
&& window
!= NULL
)
2050 item
->SetSuper(NULL
);
2054 invalidateLayout
= true;
2056 // operation not entirely successful
2064 if (invalidateLayout
) {
2066 if (locked
&& window
!= NULL
) {
2068 _UpdateWindowViewSize(false);
2081 BMenu::_RelayoutIfNeeded()
2083 if (!fUseCachedMenuLayout
) {
2084 fUseCachedMenuLayout
= true;
2094 BMenu::_LayoutItems(int32 index
)
2100 _ComputeLayout(index
, fResizeToFit
, true, &width
, &height
);
2103 ResizeTo(width
, height
);
2108 BMenu::_ValidatePreferredSize()
2110 if (!fLayoutData
->preferred
.IsWidthSet() || ResizingMode()
2111 != fLayoutData
->lastResizingMode
) {
2112 _ComputeLayout(0, true, false, NULL
, NULL
);
2113 ResetLayoutInvalidation();
2116 return fLayoutData
->preferred
;
2121 BMenu::_ComputeLayout(int32 index
, bool bestFit
, bool moveItems
,
2122 float* _width
, float* _height
)
2124 // TODO: Take "bestFit", "moveItems", "index" into account,
2125 // Recalculate only the needed items,
2126 // not the whole layout every time
2128 fLayoutData
->lastResizingMode
= ResizingMode();
2132 case B_ITEMS_IN_COLUMN
:
2135 BRect
* overrideFrame
= NULL
;
2136 if (dynamic_cast<_BMCMenuBar_
*>(Supermenu()) != NULL
) {
2137 // When the menu is modified while it's open, we get here in a
2138 // situation where trying to lock the looper would deadlock
2139 // (the window is locked waiting for the menu to terminate).
2140 // In that case, just give up on getting the supermenu bounds
2141 // and keep the menu at the current width and position.
2142 if (Supermenu()->LockLooperWithTimeout(0) == B_OK
) {
2143 parentFrame
= Supermenu()->Bounds();
2144 Supermenu()->UnlockLooper();
2145 overrideFrame
= &parentFrame
;
2149 _ComputeColumnLayout(index
, bestFit
, moveItems
, overrideFrame
,
2154 case B_ITEMS_IN_ROW
:
2155 _ComputeRowLayout(index
, bestFit
, moveItems
, frame
);
2158 case B_ITEMS_IN_MATRIX
:
2159 _ComputeMatrixLayout(frame
);
2163 // change width depending on resize mode
2165 if ((ResizingMode() & B_FOLLOW_LEFT_RIGHT
) == B_FOLLOW_LEFT_RIGHT
) {
2166 if (dynamic_cast<_BMCMenuBar_
*>(this) != NULL
)
2167 size
.width
= Bounds().Width() - fPad
.right
;
2168 else if (Parent() != NULL
)
2169 size
.width
= Parent()->Frame().Width() + 1;
2170 else if (Window() != NULL
)
2171 size
.width
= Window()->Frame().Width() + 1;
2173 size
.width
= Bounds().Width();
2175 size
.width
= frame
.Width();
2177 size
.height
= frame
.Height();
2180 *_width
= size
.width
;
2183 *_height
= size
.height
;
2186 fLayoutData
->preferred
= size
;
2189 fUseCachedMenuLayout
= true;
2194 BMenu::_ComputeColumnLayout(int32 index
, bool bestFit
, bool moveItems
,
2195 BRect
* overrideFrame
, BRect
& frame
)
2197 bool command
= false;
2198 bool control
= false;
2200 bool option
= false;
2203 frame
= ItemAt(index
- 1)->Frame();
2204 else if (overrideFrame
!= NULL
)
2205 frame
.Set(0, 0, overrideFrame
->right
, -1);
2207 frame
.Set(0, 0, 0, -1);
2212 for (; index
< fItems
.CountItems(); index
++) {
2213 BMenuItem
* item
= ItemAt(index
);
2217 item
->GetContentSize(&width
, &height
);
2219 if (item
->fModifiers
&& item
->fShortcutChar
) {
2220 width
+= font
.Size();
2221 if ((item
->fModifiers
& B_COMMAND_KEY
) != 0)
2224 if ((item
->fModifiers
& B_CONTROL_KEY
) != 0)
2227 if ((item
->fModifiers
& B_SHIFT_KEY
) != 0)
2230 if ((item
->fModifiers
& B_OPTION_KEY
) != 0)
2234 item
->fBounds
.left
= 0.0f
;
2235 item
->fBounds
.top
= frame
.bottom
+ 1.0f
;
2236 item
->fBounds
.bottom
= item
->fBounds
.top
+ height
+ fPad
.top
2239 if (item
->fSubmenu
!= NULL
)
2240 width
+= item
->Frame().Height();
2242 frame
.right
= std::max(frame
.right
, width
+ fPad
.left
+ fPad
.right
);
2243 frame
.bottom
= item
->fBounds
.bottom
;
2248 += BPrivate::MenuPrivate::MenuItemCommand()->Bounds().Width() + 1;
2252 += BPrivate::MenuPrivate::MenuItemControl()->Bounds().Width() + 1;
2256 += BPrivate::MenuPrivate::MenuItemOption()->Bounds().Width() + 1;
2260 += BPrivate::MenuPrivate::MenuItemShift()->Bounds().Width() + 1;
2263 if (fMaxContentWidth
> 0)
2264 frame
.right
= std::min(frame
.right
, fMaxContentWidth
);
2267 for (int32 i
= 0; i
< fItems
.CountItems(); i
++)
2268 ItemAt(i
)->fBounds
.right
= frame
.right
;
2272 frame
.right
= ceilf(frame
.right
);
2277 BMenu::_ComputeRowLayout(int32 index
, bool bestFit
, bool moveItems
,
2282 frame
.Set(0.0f
, 0.0f
, 0.0f
, ceilf(fh
.ascent
+ fh
.descent
+ fPad
.top
2285 for (int32 i
= 0; i
< fItems
.CountItems(); i
++) {
2286 BMenuItem
* item
= ItemAt(i
);
2288 float width
, height
;
2289 item
->GetContentSize(&width
, &height
);
2291 item
->fBounds
.left
= frame
.right
;
2292 item
->fBounds
.top
= 0.0f
;
2293 item
->fBounds
.right
= item
->fBounds
.left
+ width
+ fPad
.left
2296 frame
.right
= item
->Frame().right
+ 1.0f
;
2297 frame
.bottom
= std::max(frame
.bottom
, height
+ fPad
.top
+ fPad
.bottom
);
2301 for (int32 i
= 0; i
< fItems
.CountItems(); i
++)
2302 ItemAt(i
)->fBounds
.bottom
= frame
.bottom
;
2306 frame
.right
= ceilf(frame
.right
);
2308 frame
.right
= Bounds().right
;
2313 BMenu::_ComputeMatrixLayout(BRect
&frame
)
2315 frame
.Set(0, 0, 0, 0);
2316 for (int32 i
= 0; i
< CountItems(); i
++) {
2317 BMenuItem
* item
= ItemAt(i
);
2319 frame
.left
= std::min(frame
.left
, item
->Frame().left
);
2320 frame
.right
= std::max(frame
.right
, item
->Frame().right
);
2321 frame
.top
= std::min(frame
.top
, item
->Frame().top
);
2322 frame
.bottom
= std::max(frame
.bottom
, item
->Frame().bottom
);
2329 BMenu::LayoutInvalidated(bool descendants
)
2331 fUseCachedMenuLayout
= false;
2332 fLayoutData
->preferred
.Set(B_SIZE_UNSET
, B_SIZE_UNSET
);
2336 // Assumes the SuperMenu to be locked (due to calling ConvertToScreen())
2338 BMenu::ScreenLocation()
2340 BMenu
* superMenu
= Supermenu();
2341 BMenuItem
* superItem
= Superitem();
2343 if (superMenu
== NULL
|| superItem
== NULL
) {
2344 debugger("BMenu can't determine where to draw."
2345 "Override BMenu::ScreenLocation() to determine location.");
2349 if (superMenu
->Layout() == B_ITEMS_IN_COLUMN
)
2350 point
= superItem
->Frame().RightTop() + BPoint(1.0f
, 1.0f
);
2352 point
= superItem
->Frame().LeftBottom() + BPoint(1.0f
, 1.0f
);
2354 superMenu
->ConvertToScreen(&point
);
2361 BMenu::_CalcFrame(BPoint where
, bool* scrollOn
)
2364 BRect bounds
= Bounds();
2365 BRect frame
= bounds
.OffsetToCopy(where
);
2367 BScreen
screen(Window());
2368 BRect screenFrame
= screen
.Frame();
2370 BMenu
* superMenu
= Supermenu();
2371 BMenuItem
* superItem
= Superitem();
2373 // TODO: Horrible hack:
2374 // When added to a BMenuField, a BPopUpMenu is the child of
2375 // a _BMCMenuBar_ to "fake" the menu hierarchy
2376 bool inMenuField
= dynamic_cast<_BMCMenuBar_
*>(superMenu
) != NULL
;
2378 // Offset the menu field menu window left by the width of the checkmark
2379 // so that the text when the menu is closed lines up with the text when
2380 // the menu is open.
2382 frame
.OffsetBy(-8.0f
, 0.0f
);
2384 bool scroll
= false;
2385 if (superMenu
== NULL
|| superItem
== NULL
|| inMenuField
) {
2386 // just move the window on screen
2388 if (frame
.bottom
> screenFrame
.bottom
)
2389 frame
.OffsetBy(0, screenFrame
.bottom
- frame
.bottom
);
2390 else if (frame
.top
< screenFrame
.top
)
2391 frame
.OffsetBy(0, -frame
.top
);
2393 if (frame
.right
> screenFrame
.right
)
2394 frame
.OffsetBy(screenFrame
.right
- frame
.right
, 0);
2395 else if (frame
.left
< screenFrame
.left
)
2396 frame
.OffsetBy(-frame
.left
, 0);
2397 } else if (superMenu
->Layout() == B_ITEMS_IN_COLUMN
) {
2398 if (frame
.right
> screenFrame
.right
)
2399 frame
.OffsetBy(-superItem
->Frame().Width() - frame
.Width() - 2, 0);
2402 frame
.OffsetBy(-frame
.left
+ 6, 0);
2404 if (frame
.bottom
> screenFrame
.bottom
)
2405 frame
.OffsetBy(0, screenFrame
.bottom
- frame
.bottom
);
2407 if (frame
.bottom
> screenFrame
.bottom
) {
2408 if (scrollOn
!= NULL
&& superMenu
!= NULL
2409 && dynamic_cast<BMenuBar
*>(superMenu
) != NULL
2410 && frame
.top
< (screenFrame
.bottom
- 80)) {
2413 frame
.OffsetBy(0, -superItem
->Frame().Height()
2414 - frame
.Height() - 3);
2418 if (frame
.right
> screenFrame
.right
)
2419 frame
.OffsetBy(screenFrame
.right
- frame
.right
, 0);
2423 // basically, if this returns false, it means
2424 // that the menu frame won't fit completely inside the screen
2425 // TODO: Scrolling will currently only work up/down,
2427 scroll
= screenFrame
.Height() < frame
.Height();
2430 if (scrollOn
!= NULL
)
2438 BMenu::DrawItems(BRect updateRect
)
2440 int32 itemCount
= fItems
.CountItems();
2441 for (int32 i
= 0; i
< itemCount
; i
++) {
2442 BMenuItem
* item
= ItemAt(i
);
2443 if (item
->Frame().Intersects(updateRect
))
2450 BMenu::_State(BMenuItem
** item
) const
2452 if (fState
== MENU_STATE_TRACKING
|| fState
== MENU_STATE_CLOSED
)
2455 if (fSelected
!= NULL
&& fSelected
->Submenu() != NULL
)
2456 return fSelected
->Submenu()->_State(item
);
2463 BMenu::_InvokeItem(BMenuItem
* item
, bool now
)
2465 if (!item
->IsEnabled())
2468 // Do the "selected" animation
2469 // TODO: Doesn't work. This is supposed to highlight
2470 // and dehighlight the item, works on beos but not on haiku.
2471 if (!item
->Submenu() && LockLooper()) {
2474 Window()->UpdateIfNeeded();
2476 item
->Select(false);
2477 Window()->UpdateIfNeeded();
2480 Window()->UpdateIfNeeded();
2482 item
->Select(false);
2483 Window()->UpdateIfNeeded();
2487 // Lock the root menu window before calling BMenuItem::Invoke()
2488 BMenu
* parent
= this;
2489 BMenu
* rootMenu
= NULL
;
2492 parent
= rootMenu
->Supermenu();
2493 } while (parent
!= NULL
);
2495 if (rootMenu
->LockLooper()) {
2497 rootMenu
->UnlockLooper();
2503 BMenu::_OverSuper(BPoint location
)
2508 return fSuperbounds
.Contains(location
);
2513 BMenu::_OverSubmenu(BMenuItem
* item
, BPoint loc
)
2518 BMenu
* subMenu
= item
->Submenu();
2519 if (subMenu
== NULL
|| subMenu
->Window() == NULL
)
2522 // assume that loc is in screen coordinates
2523 if (subMenu
->Window()->Frame().Contains(loc
))
2526 return subMenu
->_OverSubmenu(subMenu
->fSelected
, loc
);
2531 BMenu::_MenuWindow()
2533 #if USE_CACHED_MENUWINDOW
2534 if (fCachedMenuWindow
== NULL
) {
2535 char windowName
[64];
2536 snprintf(windowName
, 64, "%s cached menu", Name());
2537 fCachedMenuWindow
= new (nothrow
) BMenuWindow(windowName
);
2540 return fCachedMenuWindow
;
2545 BMenu::_DeleteMenuWindow()
2547 if (fCachedMenuWindow
!= NULL
) {
2548 fCachedMenuWindow
->Lock();
2549 fCachedMenuWindow
->Quit();
2550 fCachedMenuWindow
= NULL
;
2556 BMenu::_HitTestItems(BPoint where
, BPoint slop
) const
2558 // TODO: Take "slop" into account ?
2560 // if the point doesn't lie within the menu's
2561 // bounds, bail out immediately
2562 if (!Bounds().Contains(where
))
2565 int32 itemCount
= CountItems();
2566 for (int32 i
= 0; i
< itemCount
; i
++) {
2567 BMenuItem
* item
= ItemAt(i
);
2568 if (item
->Frame().Contains(where
)
2569 && dynamic_cast<BSeparatorItem
*>(item
) == NULL
) {
2579 BMenu::_Superbounds() const
2581 return fSuperbounds
;
2586 BMenu::_CacheFontInfo()
2590 fAscent
= fh
.ascent
;
2591 fDescent
= fh
.descent
;
2592 fFontHeight
= ceilf(fh
.ascent
+ fh
.descent
+ fh
.leading
);
2597 BMenu::_ItemMarked(BMenuItem
* item
)
2599 if (IsRadioMode()) {
2600 for (int32 i
= 0; i
< CountItems(); i
++) {
2601 if (ItemAt(i
) != item
)
2602 ItemAt(i
)->SetMarked(false);
2606 if (IsLabelFromMarked() && Superitem() != NULL
)
2607 Superitem()->SetLabel(item
->Label());
2612 BMenu::_Install(BWindow
* target
)
2614 for (int32 i
= 0; i
< CountItems(); i
++)
2615 ItemAt(i
)->Install(target
);
2622 for (int32 i
= 0; i
< CountItems(); i
++)
2623 ItemAt(i
)->Uninstall();
2628 BMenu::_SelectItem(BMenuItem
* item
, bool showSubmenu
, bool selectFirstItem
,
2631 // Avoid deselecting and then reselecting the same item
2632 // which would cause flickering
2633 if (item
!= fSelected
) {
2634 if (fSelected
!= NULL
) {
2635 fSelected
->Select(false);
2636 BMenu
* subMenu
= fSelected
->Submenu();
2637 if (subMenu
!= NULL
&& subMenu
->Window() != NULL
)
2642 if (fSelected
!= NULL
)
2643 fSelected
->Select(true);
2646 if (fSelected
!= NULL
&& showSubmenu
) {
2647 BMenu
* subMenu
= fSelected
->Submenu();
2648 if (subMenu
!= NULL
&& subMenu
->Window() == NULL
) {
2649 if (!subMenu
->_Show(selectFirstItem
, keyDown
)) {
2650 // something went wrong, deselect the item
2651 fSelected
->Select(false);
2660 BMenu::_SelectNextItem(BMenuItem
* item
, bool forward
)
2662 if (CountItems() == 0) // cannot select next item in an empty menu
2665 BMenuItem
* nextItem
= _NextItem(item
, forward
);
2666 if (nextItem
== NULL
)
2669 _SelectItem(nextItem
, dynamic_cast<BMenuBar
*>(this) != NULL
);
2672 be_app
->ObscureCursor();
2681 BMenu::_NextItem(BMenuItem
* item
, bool forward
) const
2683 const int32 numItems
= fItems
.CountItems();
2687 int32 index
= fItems
.IndexOf(item
);
2688 int32 loopCount
= numItems
;
2689 while (--loopCount
) {
2690 // Cycle through menu items in the given direction...
2696 // ... wrap around...
2698 index
= numItems
- 1;
2699 else if (index
>= numItems
)
2702 // ... and return the first suitable item found.
2703 BMenuItem
* nextItem
= ItemAt(index
);
2704 if (nextItem
->IsEnabled())
2708 // If no other suitable item was found, return NULL.
2714 BMenu::_SetStickyMode(bool sticky
)
2716 if (fStickyMode
== sticky
)
2719 fStickyMode
= sticky
;
2721 if (fSuper
!= NULL
) {
2722 // propagate the status to the super menu
2723 fSuper
->_SetStickyMode(sticky
);
2725 // TODO: Ugly hack, but it needs to be done in this method
2726 BMenuBar
* menuBar
= dynamic_cast<BMenuBar
*>(this);
2727 if (sticky
&& menuBar
!= NULL
&& menuBar
->LockLooper()) {
2728 // If we are switching to sticky mode,
2729 // steal the focus from the current focus view
2730 // (needed to handle keyboard navigation)
2731 menuBar
->_StealFocus();
2732 menuBar
->UnlockLooper();
2739 BMenu::_IsStickyMode() const
2746 BMenu::_GetShiftKey(uint32
&value
) const
2748 // TODO: Move into init_interface_kit().
2749 // Currently we can't do that, as get_modifier_key() blocks forever
2750 // when called on input_server initialization, since it tries
2751 // to send a synchronous message to itself (input_server is
2754 if (get_modifier_key(B_LEFT_SHIFT_KEY
, &value
) != B_OK
)
2760 BMenu::_GetControlKey(uint32
&value
) const
2762 // TODO: Move into init_interface_kit().
2763 // Currently we can't do that, as get_modifier_key() blocks forever
2764 // when called on input_server initialization, since it tries
2765 // to send a synchronous message to itself (input_server is
2768 if (get_modifier_key(B_LEFT_CONTROL_KEY
, &value
) != B_OK
)
2774 BMenu::_GetCommandKey(uint32
&value
) const
2776 // TODO: Move into init_interface_kit().
2777 // Currently we can't do that, as get_modifier_key() blocks forever
2778 // when called on input_server initialization, since it tries
2779 // to send a synchronous message to itself (input_server is
2782 if (get_modifier_key(B_LEFT_COMMAND_KEY
, &value
) != B_OK
)
2788 BMenu::_GetOptionKey(uint32
&value
) const
2790 // TODO: Move into init_interface_kit().
2791 // Currently we can't do that, as get_modifier_key() blocks forever
2792 // when called on input_server initialization, since it tries
2793 // to send a synchronous message to itself (input_server is
2796 if (get_modifier_key(B_LEFT_OPTION_KEY
, &value
) != B_OK
)
2802 BMenu::_GetMenuKey(uint32
&value
) const
2804 // TODO: Move into init_interface_kit().
2805 // Currently we can't do that, as get_modifier_key() blocks forever
2806 // when called on input_server initialization, since it tries
2807 // to send a synchronous message to itself (input_server is
2810 if (get_modifier_key(B_MENU_KEY
, &value
) != B_OK
)
2816 BMenu::_CalcTriggers()
2818 BPrivate::TriggerList triggerList
;
2820 // Gathers the existing triggers set by the user
2821 for (int32 i
= 0; i
< CountItems(); i
++) {
2822 char trigger
= ItemAt(i
)->Trigger();
2824 triggerList
.AddTrigger(trigger
);
2827 // Set triggers for items which don't have one yet
2828 for (int32 i
= 0; i
< CountItems(); i
++) {
2829 BMenuItem
* item
= ItemAt(i
);
2830 if (item
->Trigger() == 0) {
2833 if (_ChooseTrigger(item
->Label(), index
, trigger
, triggerList
))
2834 item
->SetAutomaticTrigger(index
, trigger
);
2841 BMenu::_ChooseTrigger(const char* title
, int32
& index
, uint32
& trigger
,
2842 BPrivate::TriggerList
& triggers
)
2849 // two runs: first we look out for uppercase letters
2850 // TODO: support Unicode characters correctly!
2851 for (uint32 i
= 0; (c
= title
[i
]) != '\0'; i
++) {
2852 if (!IsInsideGlyph(c
) && isupper(c
) && !triggers
.HasTrigger(c
)) {
2854 trigger
= tolower(c
);
2855 return triggers
.AddTrigger(c
);
2859 // then, if we still haven't found anything, we accept them all
2861 while ((c
= UTF8ToCharCode(&title
)) != 0) {
2862 if (!isspace(c
) && !triggers
.HasTrigger(c
)) {
2863 trigger
= tolower(c
);
2864 return triggers
.AddTrigger(c
);
2875 BMenu::_UpdateWindowViewSize(const bool &move
)
2877 BMenuWindow
* window
= static_cast<BMenuWindow
*>(Window());
2881 if (dynamic_cast<BMenuBar
*>(this) != NULL
)
2887 bool scroll
= false;
2888 const BPoint screenLocation
= move
? ScreenLocation()
2889 : window
->Frame().LeftTop();
2890 BRect frame
= _CalcFrame(screenLocation
, &scroll
);
2891 ResizeTo(frame
.Width(), frame
.Height());
2893 if (fItems
.CountItems() > 0) {
2895 window
->ResizeTo(Bounds().Width(), Bounds().Height());
2897 BScreen
screen(window
);
2899 // If we need scrolling, resize the window to fit the screen and
2900 // attach scrollers to our cached BMenuWindow.
2901 if (dynamic_cast<BMenuBar
*>(Supermenu()) == NULL
|| frame
.top
< 0) {
2902 window
->ResizeTo(Bounds().Width(), screen
.Frame().Height());
2905 // Or, in case our parent was a BMenuBar enable scrolling with
2907 window
->ResizeTo(Bounds().Width(),
2908 screen
.Frame().bottom
- frame
.top
);
2911 if (fLayout
== B_ITEMS_IN_COLUMN
) {
2912 // we currently only support scrolling for B_ITEMS_IN_COLUMN
2913 window
->AttachScrollers();
2915 BMenuItem
* selectedItem
= FindMarked();
2916 if (selectedItem
!= NULL
) {
2917 // scroll to the selected item
2918 if (Supermenu() == NULL
) {
2919 window
->TryScrollTo(selectedItem
->Frame().top
);
2921 BPoint point
= selectedItem
->Frame().LeftTop();
2922 BPoint superPoint
= Superitem()->Frame().LeftTop();
2923 Supermenu()->ConvertToScreen(&superPoint
);
2924 ConvertToScreen(&point
);
2925 window
->TryScrollTo(point
.y
- superPoint
.y
);
2932 window
->ResizeTo(StringWidth(BPrivate::kEmptyMenuLabel
)
2933 + fPad
.left
+ fPad
.right
,
2934 fFontHeight
+ fPad
.top
+ fPad
.bottom
);
2938 window
->MoveTo(frame
.LeftTop());
2943 BMenu::_AddDynamicItems(bool keyDown
)
2945 bool addAborted
= false;
2946 if (AddDynamicItem(B_INITIAL_ADD
)) {
2947 BMenuItem
* superItem
= Superitem();
2948 BMenu
* superMenu
= Supermenu();
2950 if (superMenu
!= NULL
2951 && !superMenu
->_OkToProceed(superItem
, keyDown
)) {
2952 AddDynamicItem(B_ABORT
);
2956 } while (AddDynamicItem(B_PROCESSING
));
2964 BMenu::_OkToProceed(BMenuItem
* item
, bool keyDown
)
2968 GetMouse(&where
, &buttons
, false);
2969 bool stickyMode
= _IsStickyMode();
2970 // Quit if user clicks the mouse button in sticky mode
2971 // or releases the mouse button in nonsticky mode
2972 // or moves the pointer over another item
2973 // TODO: I added the check for BMenuBar to solve a problem with Deskbar.
2974 // BeOS seems to do something similar. This could also be a bug in
2976 if ((buttons
!= 0 && stickyMode
)
2977 || ((dynamic_cast<BMenuBar
*>(this) == NULL
2978 && (buttons
== 0 && !stickyMode
))
2979 || ((_HitTestItems(where
) != item
) && !keyDown
))) {
2988 BMenu::_CustomTrackingWantsToQuit()
2990 if (fExtraMenuData
!= NULL
&& fExtraMenuData
->trackingHook
!= NULL
2991 && fExtraMenuData
->trackingState
!= NULL
) {
2992 return fExtraMenuData
->trackingHook(this,
2993 fExtraMenuData
->trackingState
);
3001 BMenu::_QuitTracking(bool onlyThis
)
3004 if (BMenuBar
* menuBar
= dynamic_cast<BMenuBar
*>(this))
3005 menuBar
->_RestoreFocus();
3007 fState
= MENU_STATE_CLOSED
;
3010 // Close the whole menu hierarchy
3011 if (Supermenu() != NULL
)
3012 Supermenu()->fState
= MENU_STATE_CLOSED
;
3014 if (_IsStickyMode())
3015 _SetStickyMode(false);
3018 be_app
->ShowCursor();
3027 // #pragma mark - menu_info functions
3030 // TODO: Maybe the following two methods would fit better into
3031 // InterfaceDefs.cpp
3032 // In R5, they do all the work client side, we let the app_server handle the
3035 set_menu_info(menu_info
* info
)
3040 BPrivate::AppServerLink link
;
3041 link
.StartMessage(AS_SET_MENU_INFO
);
3042 link
.Attach
<menu_info
>(*info
);
3044 status_t status
= B_ERROR
;
3045 if (link
.FlushWithReply(status
) == B_OK
&& status
== B_OK
)
3046 BMenu::sMenuInfo
= *info
;
3047 // Update also the local copy, in case anyone relies on it
3054 get_menu_info(menu_info
* info
)
3059 BPrivate::AppServerLink link
;
3060 link
.StartMessage(AS_GET_MENU_INFO
);
3062 status_t status
= B_ERROR
;
3063 if (link
.FlushWithReply(status
) == B_OK
&& status
== B_OK
)
3064 link
.Read
<menu_info
>(info
);
3071 B_IF_GCC_2(InvalidateLayout__5BMenub
,_ZN5BMenu16InvalidateLayoutEb
)(
3072 BMenu
* menu
, bool descendants
)
3074 menu
->InvalidateLayout();