btrfs: Attempt to fix GCC2 build.
[haiku.git] / src / kits / interface / MenuBar.cpp
blob791cbc451a12d5ebaa3f95bca9a48fb09d4e2e7f
1 /*
2 * Copyright 2001-2015, Haiku Inc. All rights reserved.
3 * Distributed under the terms of the MIT license.
5 * Authors:
6 * Marc Flerackers (mflerackers@androme.be)
7 * Stefano Ceccherini (burton666@libero.it)
8 * Stephan Aßmus <superstippi@gmx.de>
9 */
12 #include <MenuBar.h>
14 #include <math.h>
16 #include <Application.h>
17 #include <Autolock.h>
18 #include <ControlLook.h>
19 #include <LayoutUtils.h>
20 #include <MenuItem.h>
21 #include <Window.h>
23 #include <AppMisc.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;
35 struct menubar_data {
36 BMenuBar* menuBar;
37 int32 menuIndex;
39 bool sticky;
40 bool showMenu;
42 bool useRect;
43 BRect rect;
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),
53 fTrackingPID(-1),
54 fPrevFocusToken(-1),
55 fMenuSem(-1),
56 fLastBounds(NULL),
57 fTracking(false)
59 _InitData(layout);
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,
67 layout, false),
68 fBorder(B_BORDER_FRAME),
69 fTrackingPID(-1),
70 fPrevFocusToken(-1),
71 fMenuSem(-1),
72 fLastBounds(NULL),
73 fTracking(false)
75 _InitData(layout);
79 BMenuBar::BMenuBar(BMessage* archive)
81 BMenu(archive),
82 fBorder(B_BORDER_FRAME),
83 fTrackingPID(-1),
84 fPrevFocusToken(-1),
85 fMenuSem(-1),
86 fLastBounds(NULL),
87 fTracking(false)
89 int32 border;
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);
97 _InitData(layout);
101 BMenuBar::~BMenuBar()
103 if (fTracking) {
104 status_t dummy;
105 wait_for_thread(fTrackingPID, &dummy);
108 delete fLastBounds;
112 BArchivable*
113 BMenuBar::Instantiate(BMessage* data)
115 if (validate_instantiation(data, "BMenuBar"))
116 return new BMenuBar(data);
118 return NULL;
122 status_t
123 BMenuBar::Archive(BMessage* data, bool deep) const
125 status_t err = BMenu::Archive(data, deep);
127 if (err < B_OK)
128 return err;
130 if (Border() != B_BORDER_FRAME)
131 err = data->AddInt32("_border", Border());
133 return err;
137 // #pragma mark -
140 void
141 BMenuBar::AttachedToWindow()
143 _Install(Window());
144 Window()->SetKeyMenuBar(this);
146 BMenu::AttachedToWindow();
148 *fLastBounds = Bounds();
152 void
153 BMenuBar::DetachedFromWindow()
155 BMenu::DetachedFromWindow();
159 void
160 BMenuBar::AllAttached()
162 BMenu::AllAttached();
166 void
167 BMenuBar::AllDetached()
169 BMenu::AllDetached();
173 void
174 BMenuBar::WindowActivated(bool state)
176 BView::WindowActivated(state);
180 void
181 BMenuBar::MakeFocus(bool state)
183 BMenu::MakeFocus(state);
187 // #pragma mark -
190 void
191 BMenuBar::ResizeToPreferred()
193 BMenu::ResizeToPreferred();
197 void
198 BMenuBar::GetPreferredSize(float* width, float* height)
200 BMenu::GetPreferredSize(width, height);
204 BSize
205 BMenuBar::MinSize()
207 return BMenu::MinSize();
211 BSize
212 BMenuBar::MaxSize()
214 BSize size = BMenu::MaxSize();
215 return BLayoutUtils::ComposeSize(ExplicitMaxSize(),
216 BSize(B_SIZE_UNLIMITED, size.height));
220 BSize
221 BMenuBar::PreferredSize()
223 return BMenu::PreferredSize();
227 void
228 BMenuBar::FrameMoved(BPoint newPosition)
230 BMenu::FrameMoved(newPosition);
234 void
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);
241 Invalidate(rect);
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));
248 Invalidate(rect);
251 fLastBounds->Set(0, 0, newWidth, newHeight);
253 BMenu::FrameResized(newWidth, newHeight);
257 // #pragma mark -
260 void
261 BMenuBar::Show()
263 BView::Show();
267 void
268 BMenuBar::Hide()
270 BView::Hide();
274 void
275 BMenuBar::Draw(BRect updateRect)
277 if (_RelayoutIfNeeded()) {
278 Invalidate();
279 return;
282 BRect rect(Bounds());
283 rgb_color base = LowColor();
284 uint32 flags = 0;
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,
290 0, fBorders);
292 DrawItems(updateRect);
296 // #pragma mark -
299 void
300 BMenuBar::MessageReceived(BMessage* message)
302 BMenu::MessageReceived(message);
306 void
307 BMenuBar::MouseDown(BPoint where)
309 if (fTracking)
310 return;
312 uint32 buttons;
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)
322 window->Activate();
323 window->UpdateIfNeeded();
327 StartMenuBar(-1, false, false);
331 void
332 BMenuBar::MouseUp(BPoint where)
334 BView::MouseUp(where);
338 // #pragma mark -
341 BHandler*
342 BMenuBar::ResolveSpecifier(BMessage* msg, int32 index, BMessage* specifier,
343 int32 form, const char* property)
345 return BMenu::ResolveSpecifier(msg, index, specifier, form, property);
349 status_t
350 BMenuBar::GetSupportedSuites(BMessage* data)
352 return BMenu::GetSupportedSuites(data);
356 // #pragma mark -
359 void
360 BMenuBar::SetBorder(menu_bar_border border)
362 fBorder = border;
366 menu_bar_border
367 BMenuBar::Border() const
369 return fBorder;
373 void
374 BMenuBar::SetBorders(uint32 borders)
376 fBorders = borders;
380 uint32
381 BMenuBar::Borders() const
383 return fBorders;
387 // #pragma mark -
390 status_t
391 BMenuBar::Perform(perform_code code, void* _data)
393 switch (code) {
394 case PERFORM_CODE_MIN_SIZE:
395 ((perform_data_min_size*)_data)->return_value
396 = BMenuBar::MinSize();
397 return B_OK;
399 case PERFORM_CODE_MAX_SIZE:
400 ((perform_data_max_size*)_data)->return_value
401 = BMenuBar::MaxSize();
402 return B_OK;
404 case PERFORM_CODE_PREFERRED_SIZE:
405 ((perform_data_preferred_size*)_data)->return_value
406 = BMenuBar::PreferredSize();
407 return B_OK;
409 case PERFORM_CODE_LAYOUT_ALIGNMENT:
410 ((perform_data_layout_alignment*)_data)->return_value
411 = BMenuBar::LayoutAlignment();
412 return B_OK;
414 case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH:
415 ((perform_data_has_height_for_width*)_data)->return_value
416 = BMenuBar::HasHeightForWidth();
417 return B_OK;
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,
424 &data->preferred);
425 return B_OK;
428 case PERFORM_CODE_SET_LAYOUT:
430 perform_data_set_layout* data = (perform_data_set_layout*)_data;
431 BMenuBar::SetLayout(data->layout);
432 return B_OK;
435 case PERFORM_CODE_LAYOUT_INVALIDATED:
437 perform_data_layout_invalidated* data
438 = (perform_data_layout_invalidated*)_data;
439 BMenuBar::LayoutInvalidated(data->descendants);
440 return B_OK;
443 case PERFORM_CODE_DO_LAYOUT:
445 BMenuBar::DoLayout();
446 return B_OK;
450 return BMenu::Perform(code, _data);
454 // #pragma mark -
457 void BMenuBar::_ReservedMenuBar1() {}
458 void BMenuBar::_ReservedMenuBar2() {}
459 void BMenuBar::_ReservedMenuBar3() {}
460 void BMenuBar::_ReservedMenuBar4() {}
463 BMenuBar&
464 BMenuBar::operator=(const BMenuBar &)
466 return *this;
470 // #pragma mark -
473 void
474 BMenuBar::StartMenuBar(int32 menuIndex, bool sticky, bool showMenu,
475 BRect* specialRect)
477 if (fTracking)
478 return;
480 BWindow* window = Window();
481 if (window == NULL)
482 debugger("MenuBar must be added to a window before it can be used.");
484 BAutolock lock(window);
485 if (!lock.IsLocked())
486 return;
488 fPrevFocusToken = -1;
489 fTracking = true;
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) {
501 menubar_data data;
502 data.menuBar = this;
503 data.menuIndex = menuIndex;
504 data.sticky = sticky;
505 data.showMenu = showMenu;
506 data.useRect = specialRect != NULL;
507 if (data.useRect)
508 data.rect = *specialRect;
510 resume_thread(fTrackingPID);
511 send_data(fTrackingPID, 0, &data, sizeof(data));
512 } else {
513 fTracking = false;
514 _set_menu_sem_(window, B_NO_MORE_SEMS);
515 delete_sem(fMenuSem);
520 /*static*/ int32
521 BMenuBar::_TrackTask(void* arg)
523 menubar_data data;
524 thread_id id;
525 receive_data(&id, &data, sizeof(data));
527 BMenuBar* menuBar = data.menuBar;
528 if (data.useRect)
529 menuBar->fExtraRect = &data.rect;
530 menuBar->_SetStickyMode(data.sticky);
532 int32 action;
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;
546 return 0;
550 BMenuItem*
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;
556 fChosenItem = NULL;
557 // we will use this for keyboard selection
559 BPoint where;
560 uint32 buttons;
561 if (LockLooper()) {
562 if (startIndex != -1) {
563 be_app->ObscureCursor();
564 _SelectItem(ItemAt(startIndex), true, false);
566 GetMouse(&where, &buttons);
567 UnlockLooper();
570 while (fState != MENU_STATE_CLOSED) {
571 bigtime_t snoozeAmount = 40000;
572 if (!LockLooper())
573 break;
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();
583 UnlockLooper();
584 snoozeAmount = 30000;
585 submenu->_SetStickyMode(_IsStickyMode());
586 int localAction;
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)
596 BPoint newWhere;
597 if (LockLooper()) {
598 GetMouse(&newWhere, &buttons);
599 UnlockLooper();
602 // Needed to make BMenuField child menus "sticky"
603 // (see ticket #953)
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);
610 fExtraRect = NULL;
611 } else
612 fState = MENU_STATE_CLOSED;
614 if (!LockLooper())
615 break;
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
620 _SelectItem(item);
621 } else {
622 // Menu was already opened, close it and bail
623 _SelectItem(NULL);
624 fState = MENU_STATE_CLOSED;
625 fChosenItem = NULL;
627 } else {
628 // No submenu, just select the item
629 _SelectItem(item);
631 } else if (item == NULL && fSelected != NULL
632 && !_IsStickyMode() && Bounds().Contains(where)) {
633 _SelectItem(NULL);
634 fState = MENU_STATE_TRACKING;
637 UnlockLooper();
639 if (fState != MENU_STATE_CLOSED) {
640 BPoint newWhere = where;
641 uint32 newButtons = buttons;
643 do {
644 // If user doesn't move the mouse or change buttons loop
645 // here so that we don't interfere with keyboard menu
646 // navigation
647 snooze(snoozeAmount);
648 if (!LockLooper())
649 break;
651 GetMouse(&newWhere, &newButtons);
652 UnlockLooper();
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
660 // open sub menu
661 fState = MENU_STATE_CLOSED;
662 } else
663 _SetStickyMode(false);
664 } else if (newButtons == 0 && !_IsStickyMode()) {
665 if ((fSelected != NULL && fSelected->Submenu() == NULL)
666 || item == 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;
671 } else
672 _SetStickyMode(true);
674 where = newWhere;
675 buttons = newButtons;
679 if (LockLooper()) {
680 if (fSelected != NULL)
681 _SelectItem(NULL);
683 if (fChosenItem != NULL)
684 fChosenItem->Invoke();
686 _RestoreFocus();
687 UnlockLooper();
690 if (_IsStickyMode())
691 _SetStickyMode(false);
693 _DeleteMenuWindow();
695 if (action != NULL)
696 *action = fState;
698 return fChosenItem;
702 void
703 BMenuBar::_StealFocus()
705 // We already stole the focus, don't do anything
706 if (fPrevFocusToken != -1)
707 return;
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);
714 MakeFocus();
715 window->Unlock();
720 void
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)
731 view->MakeFocus();
732 } else if (IsFocus())
733 MakeFocus(false);
735 fPrevFocusToken = -1;
736 window->Unlock();
741 void
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);