2 * Copyright 2001-2009, 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
);
291 _DrawItems(updateRect
);
299 BMenuBar::MessageReceived(BMessage
* msg
)
301 BMenu::MessageReceived(msg
);
306 BMenuBar::MouseDown(BPoint where
)
312 GetMouse(&where
, &buttons
);
314 BWindow
* window
= Window();
315 if (!window
->IsActive() || !window
->IsFront()) {
316 if ((mouse_mode() == B_FOCUS_FOLLOWS_MOUSE
)
317 || ((mouse_mode() == B_CLICK_TO_FOCUS_MOUSE
)
318 && ((buttons
& B_SECONDARY_MOUSE_BUTTON
) != 0))) {
319 // right-click to bring-to-front and send-to-back
320 // (might cause some regressions in FFM)
322 window
->UpdateIfNeeded();
326 StartMenuBar(-1, false, false);
331 BMenuBar::MouseUp(BPoint where
)
333 BView::MouseUp(where
);
341 BMenuBar::ResolveSpecifier(BMessage
* msg
, int32 index
, BMessage
* specifier
,
342 int32 form
, const char* property
)
344 return BMenu::ResolveSpecifier(msg
, index
, specifier
, form
, property
);
349 BMenuBar::GetSupportedSuites(BMessage
* data
)
351 return BMenu::GetSupportedSuites(data
);
359 BMenuBar::SetBorder(menu_bar_border border
)
366 BMenuBar::Border() const
376 BMenuBar::Perform(perform_code code
, void* _data
)
379 case PERFORM_CODE_MIN_SIZE
:
380 ((perform_data_min_size
*)_data
)->return_value
381 = BMenuBar::MinSize();
384 case PERFORM_CODE_MAX_SIZE
:
385 ((perform_data_max_size
*)_data
)->return_value
386 = BMenuBar::MaxSize();
389 case PERFORM_CODE_PREFERRED_SIZE
:
390 ((perform_data_preferred_size
*)_data
)->return_value
391 = BMenuBar::PreferredSize();
394 case PERFORM_CODE_LAYOUT_ALIGNMENT
:
395 ((perform_data_layout_alignment
*)_data
)->return_value
396 = BMenuBar::LayoutAlignment();
399 case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH
:
400 ((perform_data_has_height_for_width
*)_data
)->return_value
401 = BMenuBar::HasHeightForWidth();
404 case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH
:
406 perform_data_get_height_for_width
* data
407 = (perform_data_get_height_for_width
*)_data
;
408 BMenuBar::GetHeightForWidth(data
->width
, &data
->min
, &data
->max
,
413 case PERFORM_CODE_SET_LAYOUT
:
415 perform_data_set_layout
* data
= (perform_data_set_layout
*)_data
;
416 BMenuBar::SetLayout(data
->layout
);
420 case PERFORM_CODE_LAYOUT_INVALIDATED
:
422 perform_data_layout_invalidated
* data
423 = (perform_data_layout_invalidated
*)_data
;
424 BMenuBar::LayoutInvalidated(data
->descendants
);
428 case PERFORM_CODE_DO_LAYOUT
:
430 BMenuBar::DoLayout();
435 return BMenu::Perform(code
, _data
);
442 void BMenuBar::_ReservedMenuBar1() {}
443 void BMenuBar::_ReservedMenuBar2() {}
444 void BMenuBar::_ReservedMenuBar3() {}
445 void BMenuBar::_ReservedMenuBar4() {}
449 BMenuBar::operator=(const BMenuBar
&)
459 BMenuBar::StartMenuBar(int32 menuIndex
, bool sticky
, bool showMenu
,
465 BWindow
* window
= Window();
467 debugger("MenuBar must be added to a window before it can be used.");
469 BAutolock
lock(window
);
470 if (!lock
.IsLocked())
473 fPrevFocusToken
= -1;
476 // We are called from the window's thread,
477 // so let's call MenusBeginning() directly
478 window
->MenusBeginning();
480 fMenuSem
= create_sem(0, "window close sem");
481 _set_menu_sem_(window
, fMenuSem
);
483 fTrackingPID
= spawn_thread(_TrackTask
, "menu_tracking",
484 B_DISPLAY_PRIORITY
, NULL
);
485 if (fTrackingPID
>= 0) {
488 data
.menuIndex
= menuIndex
;
489 data
.sticky
= sticky
;
490 data
.showMenu
= showMenu
;
491 data
.useRect
= specialRect
!= NULL
;
493 data
.rect
= *specialRect
;
495 resume_thread(fTrackingPID
);
496 send_data(fTrackingPID
, 0, &data
, sizeof(data
));
499 _set_menu_sem_(window
, B_NO_MORE_SEMS
);
500 delete_sem(fMenuSem
);
506 BMenuBar::_TrackTask(void* arg
)
510 receive_data(&id
, &data
, sizeof(data
));
512 BMenuBar
* menuBar
= data
.menuBar
;
514 menuBar
->fExtraRect
= &data
.rect
;
515 menuBar
->_SetStickyMode(data
.sticky
);
518 menuBar
->_Track(&action
, data
.menuIndex
, data
.showMenu
);
520 menuBar
->fTracking
= false;
521 menuBar
->fExtraRect
= NULL
;
523 // We aren't the BWindow thread, so don't call MenusEnded() directly
524 BWindow
* window
= menuBar
->Window();
525 window
->PostMessage(_MENUS_DONE_
);
527 _set_menu_sem_(window
, B_BAD_SEM_ID
);
528 delete_sem(menuBar
->fMenuSem
);
529 menuBar
->fMenuSem
= B_BAD_SEM_ID
;
536 BMenuBar::_Track(int32
* action
, int32 startIndex
, bool showMenu
)
538 // TODO: Cleanup, merge some "if" blocks if possible
539 BMenuItem
* item
= NULL
;
540 fState
= MENU_STATE_TRACKING
;
542 // we will use this for keyboard selection
547 if (startIndex
!= -1) {
548 be_app
->ObscureCursor();
549 _SelectItem(ItemAt(startIndex
), true, false);
551 GetMouse(&where
, &buttons
);
555 while (fState
!= MENU_STATE_CLOSED
) {
556 bigtime_t snoozeAmount
= 40000;
560 item
= dynamic_cast<_BMCMenuBar_
*>(this) != NULL
? ItemAt(0)
561 : _HitTestItems(where
, B_ORIGIN
);
563 if (_OverSubmenu(fSelected
, ConvertToScreen(where
))
564 || fState
== MENU_STATE_KEY_TO_SUBMENU
) {
565 // call _Track() from the selected sub-menu when the mouse cursor
566 // is over its window
567 BMenu
* submenu
= fSelected
->Submenu();
569 snoozeAmount
= 30000;
570 submenu
->_SetStickyMode(_IsStickyMode());
572 fChosenItem
= submenu
->_Track(&localAction
);
574 // The mouse could have meen moved since the last time we
575 // checked its position, or buttons might have been pressed.
576 // Unfortunately our child menus don't tell
577 // us the new position.
578 // TODO: Maybe have a shared struct between all menus
579 // where to store the current mouse position ?
580 // (Or just use the BView mouse hooks)
583 GetMouse(&newWhere
, &buttons
);
587 // Needed to make BMenuField child menus "sticky"
589 if (localAction
== MENU_STATE_CLOSED
) {
590 if (fExtraRect
!= NULL
&& fExtraRect
->Contains(where
)
591 && point_distance(newWhere
, where
) < 9) {
592 // 9 = 3 pixels ^ 2 (since point_distance() returns the
593 // square of the distance)
594 _SetStickyMode(true);
597 fState
= MENU_STATE_CLOSED
;
601 } else if (item
!= NULL
) {
602 if (item
->Submenu() != NULL
&& item
!= fSelected
) {
603 if (item
->Submenu()->Window() == NULL
) {
604 // open the menu if it's not opened yet
607 // Menu was already opened, close it and bail
609 fState
= MENU_STATE_CLOSED
;
613 // No submenu, just select the item
616 } else if (item
== NULL
&& fSelected
!= NULL
617 && !_IsStickyMode() && Bounds().Contains(where
)) {
619 fState
= MENU_STATE_TRACKING
;
624 if (fState
!= MENU_STATE_CLOSED
) {
625 BPoint newWhere
= where
;
626 uint32 newButtons
= buttons
;
629 // If user doesn't move the mouse or change buttons loop
630 // here so that we don't interfere with keyboard menu
632 snooze(snoozeAmount
);
636 GetMouse(&newWhere
, &newButtons
);
638 } while (newWhere
== where
&& newButtons
== buttons
639 && fState
== MENU_STATE_TRACKING
);
641 if (newButtons
!= 0 && _IsStickyMode()) {
642 if (item
== NULL
|| (item
->Submenu() != NULL
643 && item
->Submenu()->Window() != NULL
)) {
644 // clicked outside the menu bar or on item with already
646 fState
= MENU_STATE_CLOSED
;
648 _SetStickyMode(false);
649 } else if (newButtons
== 0 && !_IsStickyMode()) {
650 if ((fSelected
!= NULL
&& fSelected
->Submenu() == NULL
)
652 // clicked on an item without a submenu or clicked and
653 // released the mouse button outside the menu bar
654 fChosenItem
= fSelected
;
655 fState
= MENU_STATE_CLOSED
;
657 _SetStickyMode(true);
660 buttons
= newButtons
;
665 if (fSelected
!= NULL
)
668 if (fChosenItem
!= NULL
)
669 fChosenItem
->Invoke();
676 _SetStickyMode(false);
688 BMenuBar::_StealFocus()
690 // We already stole the focus, don't do anything
691 if (fPrevFocusToken
!= -1)
694 BWindow
* window
= Window();
695 if (window
!= NULL
&& window
->Lock()) {
696 BView
* focusView
= window
->CurrentFocus();
697 if (focusView
!= NULL
&& focusView
!= this)
698 fPrevFocusToken
= _get_object_token_(focusView
);
706 BMenuBar::_RestoreFocus()
708 BWindow
* window
= Window();
709 if (window
!= NULL
&& window
->Lock()) {
710 BHandler
* handler
= NULL
;
711 if (fPrevFocusToken
!= -1
712 && gDefaultTokens
.GetToken(fPrevFocusToken
, B_HANDLER_TOKEN
,
713 (void**)&handler
) == B_OK
) {
714 BView
* view
= dynamic_cast<BView
*>(handler
);
715 if (view
!= NULL
&& view
->Window() == window
)
717 } else if (IsFocus())
720 fPrevFocusToken
= -1;
727 BMenuBar::_InitData(menu_layout layout
)
729 fLastBounds
= new BRect(Bounds());
730 SetItemMargins(8, 2, 8, 2);
731 _SetIgnoreHidden(true);