xref: /haiku/src/kits/interface/MenuBar.cpp (revision cf7c2ea69deac1215dec15334f07fddfe582b5ab)
1 /*
2  * Copyright 2001-2015, Haiku Inc. All rights reserved.
3  * Distributed under the terms of the MIT license.
4  *
5  * Authors:
6  *		Marc Flerackers (mflerackers@androme.be)
7  *		Stefano Ceccherini (burton666@libero.it)
8  *		Stephan Aßmus <superstippi@gmx.de>
9  */
10 
11 
12 #include <MenuBar.h>
13 
14 #include <math.h>
15 
16 #include <Application.h>
17 #include <Autolock.h>
18 #include <ControlLook.h>
19 #include <LayoutUtils.h>
20 #include <MenuItem.h>
21 #include <Window.h>
22 
23 #include <AppMisc.h>
24 #include <binary_compatibility/Interface.h>
25 #include <MenuPrivate.h>
26 #include <TokenSpace.h>
27 #include <InterfaceDefs.h>
28 
29 #include "BMCPrivate.h"
30 
31 
32 using BPrivate::gDefaultTokens;
33 
34 
35 struct menubar_data {
36 	BMenuBar*	menuBar;
37 	int32		menuIndex;
38 
39 	bool		sticky;
40 	bool		showMenu;
41 
42 	bool		useRect;
43 	BRect		rect;
44 };
45 
46 
BMenuBar(BRect frame,const char * name,uint32 resizingMode,menu_layout layout,bool resizeToFit)47 BMenuBar::BMenuBar(BRect frame, const char* name, uint32 resizingMode,
48 		menu_layout layout, bool resizeToFit)
49 	:
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)
58 {
59 	_InitData(layout);
60 }
61 
62 
BMenuBar(const char * name,menu_layout layout,uint32 flags)63 BMenuBar::BMenuBar(const char* name, menu_layout layout, uint32 flags)
64 	:
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)
74 {
75 	_InitData(layout);
76 }
77 
78 
BMenuBar(BMessage * archive)79 BMenuBar::BMenuBar(BMessage* archive)
80 	:
81 	BMenu(archive),
82 	fBorder(B_BORDER_FRAME),
83 	fTrackingPID(-1),
84 	fPrevFocusToken(-1),
85 	fMenuSem(-1),
86 	fLastBounds(NULL),
87 	fTracking(false)
88 {
89 	int32 border;
90 
91 	if (archive->FindInt32("_border", &border) == B_OK)
92 		SetBorder((menu_bar_border)border);
93 
94 	menu_layout layout = B_ITEMS_IN_COLUMN;
95 	archive->FindInt32("_layout", (int32*)&layout);
96 
97 	_InitData(layout);
98 }
99 
100 
~BMenuBar()101 BMenuBar::~BMenuBar()
102 {
103 	if (fTracking) {
104 		status_t dummy;
105 		wait_for_thread(fTrackingPID, &dummy);
106 	}
107 
108 	delete fLastBounds;
109 }
110 
111 
112 BArchivable*
Instantiate(BMessage * data)113 BMenuBar::Instantiate(BMessage* data)
114 {
115 	if (validate_instantiation(data, "BMenuBar"))
116 		return new BMenuBar(data);
117 
118 	return NULL;
119 }
120 
121 
122 status_t
Archive(BMessage * data,bool deep) const123 BMenuBar::Archive(BMessage* data, bool deep) const
124 {
125 	status_t err = BMenu::Archive(data, deep);
126 
127 	if (err < B_OK)
128 		return err;
129 
130 	if (Border() != B_BORDER_FRAME)
131 		err = data->AddInt32("_border", Border());
132 
133 	return err;
134 }
135 
136 
137 // #pragma mark -
138 
139 
140 void
AttachedToWindow()141 BMenuBar::AttachedToWindow()
142 {
143 	_Install(Window());
144 	Window()->SetKeyMenuBar(this);
145 
146 	BMenu::AttachedToWindow();
147 
148 	*fLastBounds = Bounds();
149 }
150 
151 
152 void
DetachedFromWindow()153 BMenuBar::DetachedFromWindow()
154 {
155 	BMenu::DetachedFromWindow();
156 }
157 
158 
159 void
AllAttached()160 BMenuBar::AllAttached()
161 {
162 	BMenu::AllAttached();
163 }
164 
165 
166 void
AllDetached()167 BMenuBar::AllDetached()
168 {
169 	BMenu::AllDetached();
170 }
171 
172 
173 void
WindowActivated(bool state)174 BMenuBar::WindowActivated(bool state)
175 {
176 	BView::WindowActivated(state);
177 }
178 
179 
180 void
MakeFocus(bool state)181 BMenuBar::MakeFocus(bool state)
182 {
183 	BMenu::MakeFocus(state);
184 }
185 
186 
187 // #pragma mark -
188 
189 
190 void
ResizeToPreferred()191 BMenuBar::ResizeToPreferred()
192 {
193 	BMenu::ResizeToPreferred();
194 }
195 
196 
197 void
GetPreferredSize(float * width,float * height)198 BMenuBar::GetPreferredSize(float* width, float* height)
199 {
200 	BMenu::GetPreferredSize(width, height);
201 }
202 
203 
204 BSize
MinSize()205 BMenuBar::MinSize()
206 {
207 	return BMenu::MinSize();
208 }
209 
210 
211 BSize
MaxSize()212 BMenuBar::MaxSize()
213 {
214 	BSize size = BMenu::MaxSize();
215 	return BLayoutUtils::ComposeSize(ExplicitMaxSize(),
216 		BSize(B_SIZE_UNLIMITED, size.height));
217 }
218 
219 
220 BSize
PreferredSize()221 BMenuBar::PreferredSize()
222 {
223 	return BMenu::PreferredSize();
224 }
225 
226 
227 void
FrameMoved(BPoint newPosition)228 BMenuBar::FrameMoved(BPoint newPosition)
229 {
230 	BMenu::FrameMoved(newPosition);
231 }
232 
233 
234 void
FrameResized(float newWidth,float newHeight)235 BMenuBar::FrameResized(float newWidth, float newHeight)
236 {
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);
242 	}
243 
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);
249 	}
250 
251 	fLastBounds->Set(0, 0, newWidth, newHeight);
252 
253 	BMenu::FrameResized(newWidth, newHeight);
254 }
255 
256 
257 // #pragma mark -
258 
259 
260 void
Show()261 BMenuBar::Show()
262 {
263 	BView::Show();
264 }
265 
266 
267 void
Hide()268 BMenuBar::Hide()
269 {
270 	BView::Hide();
271 }
272 
273 
274 void
Draw(BRect updateRect)275 BMenuBar::Draw(BRect updateRect)
276 {
277 	if (_RelayoutIfNeeded()) {
278 		Invalidate();
279 		return;
280 	}
281 
282 	BRect rect(Bounds());
283 	rgb_color base = LowColor();
284 	uint32 flags = 0;
285 
286 	be_control_look->DrawBorder(this, rect, updateRect, base,
287 		B_PLAIN_BORDER, flags, BControlLook::B_BOTTOM_BORDER);
288 
289 	be_control_look->DrawMenuBarBackground(this, rect, updateRect, base,
290 		0, fBorders);
291 
292 	DrawItems(updateRect);
293 }
294 
295 
296 // #pragma mark -
297 
298 
299 void
MessageReceived(BMessage * message)300 BMenuBar::MessageReceived(BMessage* message)
301 {
302 	BMenu::MessageReceived(message);
303 }
304 
305 
306 void
MouseDown(BPoint where)307 BMenuBar::MouseDown(BPoint where)
308 {
309 	if (fTracking)
310 		return;
311 
312 	uint32 buttons;
313 	GetMouse(&where, &buttons);
314 
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();
324 		}
325 	}
326 
327 	StartMenuBar(-1, false, false);
328 }
329 
330 
331 void
MouseUp(BPoint where)332 BMenuBar::MouseUp(BPoint where)
333 {
334 	BView::MouseUp(where);
335 }
336 
337 
338 // #pragma mark -
339 
340 
341 BHandler*
ResolveSpecifier(BMessage * msg,int32 index,BMessage * specifier,int32 form,const char * property)342 BMenuBar::ResolveSpecifier(BMessage* msg, int32 index, BMessage* specifier,
343 	int32 form, const char* property)
344 {
345 	return BMenu::ResolveSpecifier(msg, index, specifier, form, property);
346 }
347 
348 
349 status_t
GetSupportedSuites(BMessage * data)350 BMenuBar::GetSupportedSuites(BMessage* data)
351 {
352 	return BMenu::GetSupportedSuites(data);
353 }
354 
355 
356 // #pragma mark -
357 
358 
359 void
SetBorder(menu_bar_border border)360 BMenuBar::SetBorder(menu_bar_border border)
361 {
362 	fBorder = border;
363 }
364 
365 
366 menu_bar_border
Border() const367 BMenuBar::Border() const
368 {
369 	return fBorder;
370 }
371 
372 
373 void
SetBorders(uint32 borders)374 BMenuBar::SetBorders(uint32 borders)
375 {
376 	fBorders = borders;
377 }
378 
379 
380 uint32
Borders() const381 BMenuBar::Borders() const
382 {
383 	return fBorders;
384 }
385 
386 
387 // #pragma mark -
388 
389 
390 status_t
Perform(perform_code code,void * _data)391 BMenuBar::Perform(perform_code code, void* _data)
392 {
393 	switch (code) {
394 		case PERFORM_CODE_MIN_SIZE:
395 			((perform_data_min_size*)_data)->return_value
396 				= BMenuBar::MinSize();
397 			return B_OK;
398 
399 		case PERFORM_CODE_MAX_SIZE:
400 			((perform_data_max_size*)_data)->return_value
401 				= BMenuBar::MaxSize();
402 			return B_OK;
403 
404 		case PERFORM_CODE_PREFERRED_SIZE:
405 			((perform_data_preferred_size*)_data)->return_value
406 				= BMenuBar::PreferredSize();
407 			return B_OK;
408 
409 		case PERFORM_CODE_LAYOUT_ALIGNMENT:
410 			((perform_data_layout_alignment*)_data)->return_value
411 				= BMenuBar::LayoutAlignment();
412 			return B_OK;
413 
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;
418 
419 		case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH:
420 		{
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;
426 		}
427 
428 		case PERFORM_CODE_SET_LAYOUT:
429 		{
430 			perform_data_set_layout* data = (perform_data_set_layout*)_data;
431 			BMenuBar::SetLayout(data->layout);
432 			return B_OK;
433 		}
434 
435 		case PERFORM_CODE_LAYOUT_INVALIDATED:
436 		{
437 			perform_data_layout_invalidated* data
438 				= (perform_data_layout_invalidated*)_data;
439 			BMenuBar::LayoutInvalidated(data->descendants);
440 			return B_OK;
441 		}
442 
443 		case PERFORM_CODE_DO_LAYOUT:
444 		{
445 			BMenuBar::DoLayout();
446 			return B_OK;
447 		}
448 	}
449 
450 	return BMenu::Perform(code, _data);
451 }
452 
453 
454 // #pragma mark -
455 
456 
_ReservedMenuBar1()457 void BMenuBar::_ReservedMenuBar1() {}
_ReservedMenuBar2()458 void BMenuBar::_ReservedMenuBar2() {}
_ReservedMenuBar3()459 void BMenuBar::_ReservedMenuBar3() {}
_ReservedMenuBar4()460 void BMenuBar::_ReservedMenuBar4() {}
461 
462 
463 BMenuBar&
operator =(const BMenuBar &)464 BMenuBar::operator=(const BMenuBar &)
465 {
466 	return *this;
467 }
468 
469 
470 // #pragma mark -
471 
472 
473 void
StartMenuBar(int32 menuIndex,bool sticky,bool showMenu,BRect * specialRect)474 BMenuBar::StartMenuBar(int32 menuIndex, bool sticky, bool showMenu,
475 	BRect* specialRect)
476 {
477 	if (fTracking)
478 		return;
479 
480 	BWindow* window = Window();
481 	if (window == NULL)
482 		debugger("MenuBar must be added to a window before it can be used.");
483 
484 	BAutolock lock(window);
485 	if (!lock.IsLocked())
486 		return;
487 
488 	fPrevFocusToken = -1;
489 	fTracking = true;
490 
491 	// We are called from the window's thread,
492 	// so let's call MenusBeginning() directly
493 	window->MenusBeginning();
494 
495 	fMenuSem = create_sem(0, "window close sem");
496 	_set_menu_sem_(window, fMenuSem);
497 
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;
509 
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);
516 	}
517 }
518 
519 
520 /*static*/ int32
_TrackTask(void * arg)521 BMenuBar::_TrackTask(void* arg)
522 {
523 	menubar_data data;
524 	thread_id id;
525 	receive_data(&id, &data, sizeof(data));
526 
527 	BMenuBar* menuBar = data.menuBar;
528 	if (data.useRect)
529 		menuBar->fExtraRect = &data.rect;
530 	menuBar->_SetStickyMode(data.sticky);
531 
532 	int32 action;
533 	menuBar->_Track(&action, data.menuIndex, data.showMenu);
534 
535 	menuBar->fTracking = false;
536 	menuBar->fExtraRect = NULL;
537 
538 	// We aren't the BWindow thread, so don't call MenusEnded() directly
539 	BWindow* window = menuBar->Window();
540 	window->PostMessage(_MENUS_DONE_);
541 
542 	_set_menu_sem_(window, B_BAD_SEM_ID);
543 	delete_sem(menuBar->fMenuSem);
544 	menuBar->fMenuSem = B_BAD_SEM_ID;
545 
546 	return 0;
547 }
548 
549 
550 BMenuItem*
_Track(int32 * action,int32 startIndex,bool showMenu)551 BMenuBar::_Track(int32* action, int32 startIndex, bool showMenu)
552 {
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
558 
559 	BPoint where;
560 	uint32 buttons;
561 	if (LockLooper()) {
562 		if (startIndex != -1) {
563 			be_app->ObscureCursor();
564 			_SelectItem(ItemAt(startIndex), true, false);
565 		}
566 		GetMouse(&where, &buttons);
567 		UnlockLooper();
568 	}
569 
570 	while (fState != MENU_STATE_CLOSED) {
571 		bigtime_t snoozeAmount = 40000;
572 		if (!LockLooper())
573 			break;
574 
575 		item = dynamic_cast<_BMCMenuBar_*>(this) != NULL ? ItemAt(0)
576 			: _HitTestItems(where, B_ORIGIN);
577 
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);
588 
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();
600 			}
601 
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;
613 			}
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;
626 				}
627 			} else {
628 				// No submenu, just select the item
629 				_SelectItem(item);
630 			}
631 		} else if (item == NULL && fSelected != NULL
632 			&& !_IsStickyMode() && Bounds().Contains(where)) {
633 			_SelectItem(NULL);
634 			fState = MENU_STATE_TRACKING;
635 		}
636 
637 		UnlockLooper();
638 
639 		if (fState != MENU_STATE_CLOSED) {
640 			BPoint newWhere = where;
641 			uint32 newButtons = buttons;
642 
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;
650 
651 				GetMouse(&newWhere, &newButtons);
652 				UnlockLooper();
653 			} while (newWhere == where && newButtons == buttons
654 				&& fState == MENU_STATE_TRACKING);
655 
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);
673 			}
674 			where = newWhere;
675 			buttons = newButtons;
676 		}
677 	}
678 
679 	if (LockLooper()) {
680 		if (fSelected != NULL)
681 			_SelectItem(NULL);
682 
683 		if (fChosenItem != NULL)
684 			fChosenItem->Invoke();
685 
686 		_RestoreFocus();
687 		UnlockLooper();
688 	}
689 
690 	if (_IsStickyMode())
691 		_SetStickyMode(false);
692 
693 	_DeleteMenuWindow();
694 
695 	if (action != NULL)
696 		*action = fState;
697 
698 	return fChosenItem;
699 }
700 
701 
702 void
_StealFocus()703 BMenuBar::_StealFocus()
704 {
705 	// We already stole the focus, don't do anything
706 	if (fPrevFocusToken != -1)
707 		return;
708 
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();
716 	}
717 }
718 
719 
720 void
_RestoreFocus()721 BMenuBar::_RestoreFocus()
722 {
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);
734 
735 		fPrevFocusToken = -1;
736 		window->Unlock();
737 	}
738 }
739 
740 
741 void
_InitData(menu_layout layout)742 BMenuBar::_InitData(menu_layout layout)
743 {
744 	const float fontSize = be_plain_font->Size();
745 	float lr = fontSize * 2.0f / 3.0f, tb = fontSize / 6.0f;
746 	SetItemMargins(lr, tb, lr, tb);
747 
748 	fBorders = BControlLook::B_ALL_BORDERS;
749 	fLastBounds = new BRect(Bounds());
750 	_SetIgnoreHidden(true);
751 	SetLowUIColor(B_MENU_BACKGROUND_COLOR);
752 	SetViewColor(B_TRANSPARENT_COLOR);
753 }
754