2 * Copyright 2001-2015, Haiku Inc. All rights reserved.
3 * Distributed under the terms of the MIT license.
6 * Marc Flerackers (mflerackers@androme.be)
7 * Stefano Ceccherini (burton666@libero.it)
8 * Stephan Aßmus <superstippi@gmx.de>
16 #include <Application.h>
18 #include <ControlLook.h>
19 #include <LayoutUtils.h>
24 #include <binary_compatibility/Interface.h>
25 #include <MenuPrivate.h>
26 #include <TokenSpace.h>
27 #include <InterfaceDefs.h>
29 #include "BMCPrivate.h"
32 using BPrivate::gDefaultTokens
;
47 BMenuBar::BMenuBar(BRect frame
, const char* name
, uint32 resizingMode
,
48 menu_layout layout
, bool resizeToFit
)
50 BMenu(frame
, name
, resizingMode
, B_WILL_DRAW
| B_FRAME_EVENTS
51 | B_FULL_UPDATE_ON_RESIZE
, layout
, resizeToFit
),
52 fBorder(B_BORDER_FRAME
),
63 BMenuBar::BMenuBar(const char* name
, menu_layout layout
, uint32 flags
)
65 BMenu(BRect(), name
, B_FOLLOW_NONE
,
66 flags
| B_WILL_DRAW
| B_FRAME_EVENTS
| B_SUPPORTS_LAYOUT
,
68 fBorder(B_BORDER_FRAME
),
79 BMenuBar::BMenuBar(BMessage
* archive
)
82 fBorder(B_BORDER_FRAME
),
91 if (archive
->FindInt32("_border", &border
) == B_OK
)
92 SetBorder((menu_bar_border
)border
);
94 menu_layout layout
= B_ITEMS_IN_COLUMN
;
95 archive
->FindInt32("_layout", (int32
*)&layout
);
101 BMenuBar::~BMenuBar()
105 wait_for_thread(fTrackingPID
, &dummy
);
113 BMenuBar::Instantiate(BMessage
* data
)
115 if (validate_instantiation(data
, "BMenuBar"))
116 return new BMenuBar(data
);
123 BMenuBar::Archive(BMessage
* data
, bool deep
) const
125 status_t err
= BMenu::Archive(data
, deep
);
130 if (Border() != B_BORDER_FRAME
)
131 err
= data
->AddInt32("_border", Border());
141 BMenuBar::AttachedToWindow()
144 Window()->SetKeyMenuBar(this);
146 BMenu::AttachedToWindow();
148 *fLastBounds
= Bounds();
153 BMenuBar::DetachedFromWindow()
155 BMenu::DetachedFromWindow();
160 BMenuBar::AllAttached()
162 BMenu::AllAttached();
167 BMenuBar::AllDetached()
169 BMenu::AllDetached();
174 BMenuBar::WindowActivated(bool state
)
176 BView::WindowActivated(state
);
181 BMenuBar::MakeFocus(bool state
)
183 BMenu::MakeFocus(state
);
191 BMenuBar::ResizeToPreferred()
193 BMenu::ResizeToPreferred();
198 BMenuBar::GetPreferredSize(float* width
, float* height
)
200 BMenu::GetPreferredSize(width
, height
);
207 return BMenu::MinSize();
214 BSize size
= BMenu::MaxSize();
215 return BLayoutUtils::ComposeSize(ExplicitMaxSize(),
216 BSize(B_SIZE_UNLIMITED
, size
.height
));
221 BMenuBar::PreferredSize()
223 return BMenu::PreferredSize();
228 BMenuBar::FrameMoved(BPoint newPosition
)
230 BMenu::FrameMoved(newPosition
);
235 BMenuBar::FrameResized(float newWidth
, float newHeight
)
237 // invalidate right border
238 if (newWidth
!= fLastBounds
->Width()) {
239 BRect
rect(min_c(fLastBounds
->right
, newWidth
), 0,
240 max_c(fLastBounds
->right
, newWidth
), newHeight
);
244 // invalidate bottom border
245 if (newHeight
!= fLastBounds
->Height()) {
246 BRect
rect(0, min_c(fLastBounds
->bottom
, newHeight
) - 1,
247 newWidth
, max_c(fLastBounds
->bottom
, newHeight
));
251 fLastBounds
->Set(0, 0, newWidth
, newHeight
);
253 BMenu::FrameResized(newWidth
, newHeight
);
275 BMenuBar::Draw(BRect updateRect
)
277 if (_RelayoutIfNeeded()) {
282 BRect
rect(Bounds());
283 rgb_color base
= LowColor();
286 be_control_look
->DrawBorder(this, rect
, updateRect
, base
,
287 B_PLAIN_BORDER
, flags
, BControlLook::B_BOTTOM_BORDER
);
289 be_control_look
->DrawMenuBarBackground(this, rect
, updateRect
, base
,
292 DrawItems(updateRect
);
300 BMenuBar::MessageReceived(BMessage
* message
)
302 BMenu::MessageReceived(message
);
307 BMenuBar::MouseDown(BPoint where
)
313 GetMouse(&where
, &buttons
);
315 BWindow
* window
= Window();
316 if (!window
->IsActive() || !window
->IsFront()) {
317 if ((mouse_mode() == B_FOCUS_FOLLOWS_MOUSE
)
318 || ((mouse_mode() == B_CLICK_TO_FOCUS_MOUSE
)
319 && ((buttons
& B_SECONDARY_MOUSE_BUTTON
) != 0))) {
320 // right-click to bring-to-front and send-to-back
321 // (might cause some regressions in FFM)
323 window
->UpdateIfNeeded();
327 StartMenuBar(-1, false, false);
332 BMenuBar::MouseUp(BPoint where
)
334 BView::MouseUp(where
);
342 BMenuBar::ResolveSpecifier(BMessage
* msg
, int32 index
, BMessage
* specifier
,
343 int32 form
, const char* property
)
345 return BMenu::ResolveSpecifier(msg
, index
, specifier
, form
, property
);
350 BMenuBar::GetSupportedSuites(BMessage
* data
)
352 return BMenu::GetSupportedSuites(data
);
360 BMenuBar::SetBorder(menu_bar_border border
)
367 BMenuBar::Border() const
374 BMenuBar::SetBorders(uint32 borders
)
381 BMenuBar::Borders() const
391 BMenuBar::Perform(perform_code code
, void* _data
)
394 case PERFORM_CODE_MIN_SIZE
:
395 ((perform_data_min_size
*)_data
)->return_value
396 = BMenuBar::MinSize();
399 case PERFORM_CODE_MAX_SIZE
:
400 ((perform_data_max_size
*)_data
)->return_value
401 = BMenuBar::MaxSize();
404 case PERFORM_CODE_PREFERRED_SIZE
:
405 ((perform_data_preferred_size
*)_data
)->return_value
406 = BMenuBar::PreferredSize();
409 case PERFORM_CODE_LAYOUT_ALIGNMENT
:
410 ((perform_data_layout_alignment
*)_data
)->return_value
411 = BMenuBar::LayoutAlignment();
414 case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH
:
415 ((perform_data_has_height_for_width
*)_data
)->return_value
416 = BMenuBar::HasHeightForWidth();
419 case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH
:
421 perform_data_get_height_for_width
* data
422 = (perform_data_get_height_for_width
*)_data
;
423 BMenuBar::GetHeightForWidth(data
->width
, &data
->min
, &data
->max
,
428 case PERFORM_CODE_SET_LAYOUT
:
430 perform_data_set_layout
* data
= (perform_data_set_layout
*)_data
;
431 BMenuBar::SetLayout(data
->layout
);
435 case PERFORM_CODE_LAYOUT_INVALIDATED
:
437 perform_data_layout_invalidated
* data
438 = (perform_data_layout_invalidated
*)_data
;
439 BMenuBar::LayoutInvalidated(data
->descendants
);
443 case PERFORM_CODE_DO_LAYOUT
:
445 BMenuBar::DoLayout();
450 return BMenu::Perform(code
, _data
);
457 void BMenuBar::_ReservedMenuBar1() {}
458 void BMenuBar::_ReservedMenuBar2() {}
459 void BMenuBar::_ReservedMenuBar3() {}
460 void BMenuBar::_ReservedMenuBar4() {}
464 BMenuBar::operator=(const BMenuBar
&)
474 BMenuBar::StartMenuBar(int32 menuIndex
, bool sticky
, bool showMenu
,
480 BWindow
* window
= Window();
482 debugger("MenuBar must be added to a window before it can be used.");
484 BAutolock
lock(window
);
485 if (!lock
.IsLocked())
488 fPrevFocusToken
= -1;
491 // We are called from the window's thread,
492 // so let's call MenusBeginning() directly
493 window
->MenusBeginning();
495 fMenuSem
= create_sem(0, "window close sem");
496 _set_menu_sem_(window
, fMenuSem
);
498 fTrackingPID
= spawn_thread(_TrackTask
, "menu_tracking",
499 B_DISPLAY_PRIORITY
, NULL
);
500 if (fTrackingPID
>= 0) {
503 data
.menuIndex
= menuIndex
;
504 data
.sticky
= sticky
;
505 data
.showMenu
= showMenu
;
506 data
.useRect
= specialRect
!= NULL
;
508 data
.rect
= *specialRect
;
510 resume_thread(fTrackingPID
);
511 send_data(fTrackingPID
, 0, &data
, sizeof(data
));
514 _set_menu_sem_(window
, B_NO_MORE_SEMS
);
515 delete_sem(fMenuSem
);
521 BMenuBar::_TrackTask(void* arg
)
525 receive_data(&id
, &data
, sizeof(data
));
527 BMenuBar
* menuBar
= data
.menuBar
;
529 menuBar
->fExtraRect
= &data
.rect
;
530 menuBar
->_SetStickyMode(data
.sticky
);
533 menuBar
->_Track(&action
, data
.menuIndex
, data
.showMenu
);
535 menuBar
->fTracking
= false;
536 menuBar
->fExtraRect
= NULL
;
538 // We aren't the BWindow thread, so don't call MenusEnded() directly
539 BWindow
* window
= menuBar
->Window();
540 window
->PostMessage(_MENUS_DONE_
);
542 _set_menu_sem_(window
, B_BAD_SEM_ID
);
543 delete_sem(menuBar
->fMenuSem
);
544 menuBar
->fMenuSem
= B_BAD_SEM_ID
;
551 BMenuBar::_Track(int32
* action
, int32 startIndex
, bool showMenu
)
553 // TODO: Cleanup, merge some "if" blocks if possible
554 BMenuItem
* item
= NULL
;
555 fState
= MENU_STATE_TRACKING
;
557 // we will use this for keyboard selection
562 if (startIndex
!= -1) {
563 be_app
->ObscureCursor();
564 _SelectItem(ItemAt(startIndex
), true, false);
566 GetMouse(&where
, &buttons
);
570 while (fState
!= MENU_STATE_CLOSED
) {
571 bigtime_t snoozeAmount
= 40000;
575 item
= dynamic_cast<_BMCMenuBar_
*>(this) != NULL
? ItemAt(0)
576 : _HitTestItems(where
, B_ORIGIN
);
578 if (_OverSubmenu(fSelected
, ConvertToScreen(where
))
579 || fState
== MENU_STATE_KEY_TO_SUBMENU
) {
580 // call _Track() from the selected sub-menu when the mouse cursor
581 // is over its window
582 BMenu
* submenu
= fSelected
->Submenu();
584 snoozeAmount
= 30000;
585 submenu
->_SetStickyMode(_IsStickyMode());
587 fChosenItem
= submenu
->_Track(&localAction
);
589 // The mouse could have meen moved since the last time we
590 // checked its position, or buttons might have been pressed.
591 // Unfortunately our child menus don't tell
592 // us the new position.
593 // TODO: Maybe have a shared struct between all menus
594 // where to store the current mouse position ?
595 // (Or just use the BView mouse hooks)
598 GetMouse(&newWhere
, &buttons
);
602 // Needed to make BMenuField child menus "sticky"
604 if (localAction
== MENU_STATE_CLOSED
) {
605 if (fExtraRect
!= NULL
&& fExtraRect
->Contains(where
)
606 && point_distance(newWhere
, where
) < 9) {
607 // 9 = 3 pixels ^ 2 (since point_distance() returns the
608 // square of the distance)
609 _SetStickyMode(true);
612 fState
= MENU_STATE_CLOSED
;
616 } else if (item
!= NULL
) {
617 if (item
->Submenu() != NULL
&& item
!= fSelected
) {
618 if (item
->Submenu()->Window() == NULL
) {
619 // open the menu if it's not opened yet
622 // Menu was already opened, close it and bail
624 fState
= MENU_STATE_CLOSED
;
628 // No submenu, just select the item
631 } else if (item
== NULL
&& fSelected
!= NULL
632 && !_IsStickyMode() && Bounds().Contains(where
)) {
634 fState
= MENU_STATE_TRACKING
;
639 if (fState
!= MENU_STATE_CLOSED
) {
640 BPoint newWhere
= where
;
641 uint32 newButtons
= buttons
;
644 // If user doesn't move the mouse or change buttons loop
645 // here so that we don't interfere with keyboard menu
647 snooze(snoozeAmount
);
651 GetMouse(&newWhere
, &newButtons
);
653 } while (newWhere
== where
&& newButtons
== buttons
654 && fState
== MENU_STATE_TRACKING
);
656 if (newButtons
!= 0 && _IsStickyMode()) {
657 if (item
== NULL
|| (item
->Submenu() != NULL
658 && item
->Submenu()->Window() != NULL
)) {
659 // clicked outside the menu bar or on item with already
661 fState
= MENU_STATE_CLOSED
;
663 _SetStickyMode(false);
664 } else if (newButtons
== 0 && !_IsStickyMode()) {
665 if ((fSelected
!= NULL
&& fSelected
->Submenu() == NULL
)
667 // clicked on an item without a submenu or clicked and
668 // released the mouse button outside the menu bar
669 fChosenItem
= fSelected
;
670 fState
= MENU_STATE_CLOSED
;
672 _SetStickyMode(true);
675 buttons
= newButtons
;
680 if (fSelected
!= NULL
)
683 if (fChosenItem
!= NULL
)
684 fChosenItem
->Invoke();
691 _SetStickyMode(false);
703 BMenuBar::_StealFocus()
705 // We already stole the focus, don't do anything
706 if (fPrevFocusToken
!= -1)
709 BWindow
* window
= Window();
710 if (window
!= NULL
&& window
->Lock()) {
711 BView
* focusView
= window
->CurrentFocus();
712 if (focusView
!= NULL
&& focusView
!= this)
713 fPrevFocusToken
= _get_object_token_(focusView
);
721 BMenuBar::_RestoreFocus()
723 BWindow
* window
= Window();
724 if (window
!= NULL
&& window
->Lock()) {
725 BHandler
* handler
= NULL
;
726 if (fPrevFocusToken
!= -1
727 && gDefaultTokens
.GetToken(fPrevFocusToken
, B_HANDLER_TOKEN
,
728 (void**)&handler
) == B_OK
) {
729 BView
* view
= dynamic_cast<BView
*>(handler
);
730 if (view
!= NULL
&& view
->Window() == window
)
732 } else if (IsFocus())
735 fPrevFocusToken
= -1;
742 BMenuBar::_InitData(menu_layout layout
)
744 fBorders
= BControlLook::B_ALL_BORDERS
;
745 fLastBounds
= new BRect(Bounds());
746 SetItemMargins(8.0f
, 2.0f
, 8.0f
, 2.0f
);
747 _SetIgnoreHidden(true);
748 SetLowUIColor(B_MENU_BACKGROUND_COLOR
);
749 SetViewColor(B_TRANSPARENT_COLOR
);