xref: /haiku/src/kits/interface/MenuBar.cpp (revision 7749d0bb0c358a3279b1b9cc76d8376e900130a5)
1 /*
2  * Copyright 2001-2009, 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 
47 BMenuBar::BMenuBar(BRect frame, const char* title, uint32 resizeMask,
48 		menu_layout layout, bool resizeToFit)
49 	:
50 	BMenu(frame, title, resizeMask, B_WILL_DRAW | B_FRAME_EVENTS, layout,
51 		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 
63 BMenuBar::BMenuBar(const char* title, menu_layout layout, uint32 flags)
64 	:
65 	BMenu(BRect(), title, 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 
79 BMenuBar::BMenuBar(BMessage* data)
80 	:
81 	BMenu(data),
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 (data->FindInt32("_border", &border) == B_OK)
92 		SetBorder((menu_bar_border)border);
93 
94 	menu_layout layout = B_ITEMS_IN_COLUMN;
95 	data->FindInt32("_layout", (int32*)&layout);
96 
97 	_InitData(layout);
98 }
99 
100 
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*
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
123 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
141 BMenuBar::AttachedToWindow()
142 {
143 	_Install(Window());
144 	Window()->SetKeyMenuBar(this);
145 
146 	BMenu::AttachedToWindow();
147 
148 	*fLastBounds = Bounds();
149 }
150 
151 
152 void
153 BMenuBar::DetachedFromWindow()
154 {
155 	BMenu::DetachedFromWindow();
156 }
157 
158 
159 void
160 BMenuBar::AllAttached()
161 {
162 	BMenu::AllAttached();
163 }
164 
165 
166 void
167 BMenuBar::AllDetached()
168 {
169 	BMenu::AllDetached();
170 }
171 
172 
173 void
174 BMenuBar::WindowActivated(bool state)
175 {
176 	BView::WindowActivated(state);
177 }
178 
179 
180 void
181 BMenuBar::MakeFocus(bool state)
182 {
183 	BMenu::MakeFocus(state);
184 }
185 
186 
187 // #pragma mark -
188 
189 
190 void
191 BMenuBar::ResizeToPreferred()
192 {
193 	BMenu::ResizeToPreferred();
194 }
195 
196 
197 void
198 BMenuBar::GetPreferredSize(float* width, float* height)
199 {
200 	BMenu::GetPreferredSize(width, height);
201 }
202 
203 
204 BSize
205 BMenuBar::MinSize()
206 {
207 	return BMenu::MinSize();
208 }
209 
210 
211 BSize
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
221 BMenuBar::PreferredSize()
222 {
223 	return BMenu::PreferredSize();
224 }
225 
226 
227 void
228 BMenuBar::FrameMoved(BPoint newPosition)
229 {
230 	BMenu::FrameMoved(newPosition);
231 }
232 
233 
234 void
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
261 BMenuBar::Show()
262 {
263 	BView::Show();
264 }
265 
266 
267 void
268 BMenuBar::Hide()
269 {
270 	BView::Hide();
271 }
272 
273 
274 void
275 BMenuBar::Draw(BRect updateRect)
276 {
277 	if (_RelayoutIfNeeded()) {
278 		Invalidate();
279 		return;
280 	}
281 
282 	if (be_control_look != NULL) {
283 		BRect rect(Bounds());
284 		rgb_color base = LowColor();
285 		uint32 flags = 0;
286 
287 		be_control_look->DrawBorder(this, rect, updateRect, base,
288 			B_PLAIN_BORDER, flags, BControlLook::B_BOTTOM_BORDER);
289 
290 		be_control_look->DrawMenuBarBackground(this, rect, updateRect, base);
291 
292 		_DrawItems(updateRect);
293 		return;
294 	}
295 
296 	// TODO: implement additional border styles
297 	rgb_color color = HighColor();
298 
299 	BRect bounds(Bounds());
300 	// Restore the background of the previously selected menuitem
301 	DrawBackground(bounds & updateRect);
302 
303 	rgb_color noTint = LowColor();
304 
305 	SetHighColor(tint_color(noTint, B_LIGHTEN_2_TINT));
306 	StrokeLine(BPoint(0.0f, bounds.bottom - 2.0f), BPoint(0.0f, 0.0f));
307 	StrokeLine(BPoint(bounds.right, 0.0f));
308 
309 	SetHighColor(tint_color(noTint, B_DARKEN_1_TINT));
310 	StrokeLine(BPoint(1.0f, bounds.bottom - 1.0f),
311 		BPoint(bounds.right, bounds.bottom - 1.0f));
312 
313 	SetHighColor(tint_color(noTint, B_DARKEN_2_TINT));
314 	StrokeLine(BPoint(0.0f, bounds.bottom),
315 		BPoint(bounds.right, bounds.bottom));
316 	StrokeLine(BPoint(bounds.right, 0.0f), BPoint(bounds.right, bounds.bottom));
317 
318 	SetHighColor(color);
319 		// revert to previous used color (cheap PushState()/PopState())
320 
321 	_DrawItems(updateRect);
322 }
323 
324 
325 // #pragma mark -
326 
327 
328 void
329 BMenuBar::MessageReceived(BMessage* msg)
330 {
331 	BMenu::MessageReceived(msg);
332 }
333 
334 
335 void
336 BMenuBar::MouseDown(BPoint where)
337 {
338 	if (fTracking)
339 		return;
340 
341 	uint32 buttons;
342 	GetMouse(&where, &buttons);
343 
344   	BWindow* window = Window();
345   	if (!window->IsActive() || !window->IsFront()) {
346 		if ((mouse_mode() == B_FOCUS_FOLLOWS_MOUSE)
347 			|| ((mouse_mode() == B_CLICK_TO_FOCUS_MOUSE)
348 				&& ((buttons & B_SECONDARY_MOUSE_BUTTON) != 0))) {
349 			window->Activate();
350 			window->UpdateIfNeeded();
351 		}
352 	}
353 
354 	StartMenuBar(-1, false, false);
355 }
356 
357 
358 void
359 BMenuBar::MouseUp(BPoint where)
360 {
361 	BView::MouseUp(where);
362 }
363 
364 
365 // #pragma mark -
366 
367 
368 BHandler*
369 BMenuBar::ResolveSpecifier(BMessage* msg, int32 index, BMessage* specifier,
370 	int32 form, const char* property)
371 {
372 	return BMenu::ResolveSpecifier(msg, index, specifier, form, property);
373 }
374 
375 
376 status_t
377 BMenuBar::GetSupportedSuites(BMessage* data)
378 {
379 	return BMenu::GetSupportedSuites(data);
380 }
381 
382 
383 // #pragma mark -
384 
385 
386 void
387 BMenuBar::SetBorder(menu_bar_border border)
388 {
389 	fBorder = border;
390 }
391 
392 
393 menu_bar_border
394 BMenuBar::Border() const
395 {
396 	return fBorder;
397 }
398 
399 
400 // #pragma mark -
401 
402 
403 status_t
404 BMenuBar::Perform(perform_code code, void* _data)
405 {
406 	switch (code) {
407 		case PERFORM_CODE_MIN_SIZE:
408 			((perform_data_min_size*)_data)->return_value
409 				= BMenuBar::MinSize();
410 			return B_OK;
411 		case PERFORM_CODE_MAX_SIZE:
412 			((perform_data_max_size*)_data)->return_value
413 				= BMenuBar::MaxSize();
414 			return B_OK;
415 		case PERFORM_CODE_PREFERRED_SIZE:
416 			((perform_data_preferred_size*)_data)->return_value
417 				= BMenuBar::PreferredSize();
418 			return B_OK;
419 		case PERFORM_CODE_LAYOUT_ALIGNMENT:
420 			((perform_data_layout_alignment*)_data)->return_value
421 				= BMenuBar::LayoutAlignment();
422 			return B_OK;
423 		case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH:
424 			((perform_data_has_height_for_width*)_data)->return_value
425 				= BMenuBar::HasHeightForWidth();
426 			return B_OK;
427 		case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH:
428 		{
429 			perform_data_get_height_for_width* data
430 				= (perform_data_get_height_for_width*)_data;
431 			BMenuBar::GetHeightForWidth(data->width, &data->min, &data->max,
432 				&data->preferred);
433 			return B_OK;
434 		}
435 		case PERFORM_CODE_SET_LAYOUT:
436 		{
437 			perform_data_set_layout* data = (perform_data_set_layout*)_data;
438 			BMenuBar::SetLayout(data->layout);
439 			return B_OK;
440 		}
441 		case PERFORM_CODE_INVALIDATE_LAYOUT:
442 		{
443 			perform_data_invalidate_layout* data
444 				= (perform_data_invalidate_layout*)_data;
445 			BMenuBar::InvalidateLayout(data->descendants);
446 			return B_OK;
447 		}
448 		case PERFORM_CODE_DO_LAYOUT:
449 		{
450 			BMenuBar::DoLayout();
451 			return B_OK;
452 		}
453 	}
454 
455 	return BMenu::Perform(code, _data);
456 }
457 
458 
459 // #pragma mark -
460 
461 
462 void BMenuBar::_ReservedMenuBar1() {}
463 void BMenuBar::_ReservedMenuBar2() {}
464 void BMenuBar::_ReservedMenuBar3() {}
465 void BMenuBar::_ReservedMenuBar4() {}
466 
467 
468 BMenuBar &
469 BMenuBar::operator=(const BMenuBar &)
470 {
471 	return *this;
472 }
473 
474 
475 // #pragma mark -
476 
477 
478 void
479 BMenuBar::StartMenuBar(int32 menuIndex, bool sticky, bool showMenu,
480 	BRect* specialRect)
481 {
482 	if (fTracking)
483 		return;
484 
485 	BWindow* window = Window();
486 	if (window == NULL)
487 		debugger("MenuBar must be added to a window before it can be used.");
488 
489 	BAutolock lock(window);
490 	if (!lock.IsLocked())
491 		return;
492 
493 	fPrevFocusToken = -1;
494 	fTracking = true;
495 
496 	// We are called from the window's thread,
497 	// so let's call MenusBeginning() directly
498 	window->MenusBeginning();
499 
500 	fMenuSem = create_sem(0, "window close sem");
501 	_set_menu_sem_(window, fMenuSem);
502 
503 	fTrackingPID = spawn_thread(_TrackTask, "menu_tracking", B_DISPLAY_PRIORITY,
504 		NULL);
505 	if (fTrackingPID >= 0) {
506 		menubar_data data;
507 		data.menuBar = this;
508 		data.menuIndex = menuIndex;
509 		data.sticky = sticky;
510 		data.showMenu = showMenu;
511 		data.useRect = specialRect != NULL;
512 		if (data.useRect)
513 			data.rect = *specialRect;
514 
515 		resume_thread(fTrackingPID);
516 		send_data(fTrackingPID, 0, &data, sizeof(data));
517 	} else {
518 		fTracking = false;
519 		_set_menu_sem_(window, B_NO_MORE_SEMS);
520 		delete_sem(fMenuSem);
521 	}
522 }
523 
524 
525 /*static*/ int32
526 BMenuBar::_TrackTask(void* arg)
527 {
528 	menubar_data data;
529 	thread_id id;
530 	receive_data(&id, &data, sizeof(data));
531 
532 	BMenuBar* menuBar = data.menuBar;
533 	if (data.useRect)
534 		menuBar->fExtraRect = &data.rect;
535 	menuBar->_SetStickyMode(data.sticky);
536 
537 	int32 action;
538 	menuBar->_Track(&action, data.menuIndex, data.showMenu);
539 
540 	menuBar->fTracking = false;
541 	menuBar->fExtraRect = NULL;
542 
543 	// We aren't the BWindow thread, so don't call MenusEnded() directly
544 	BWindow* window = menuBar->Window();
545 	window->PostMessage(_MENUS_DONE_);
546 
547 	_set_menu_sem_(window, B_BAD_SEM_ID);
548 	delete_sem(menuBar->fMenuSem);
549 	menuBar->fMenuSem = B_BAD_SEM_ID;
550 
551 	return 0;
552 }
553 
554 
555 BMenuItem*
556 BMenuBar::_Track(int32* action, int32 startIndex, bool showMenu)
557 {
558 	// TODO: Cleanup, merge some "if" blocks if possible
559 	fChosenItem = NULL;
560 
561 	BWindow* window = Window();
562 	fState = MENU_STATE_TRACKING;
563 
564 	BPoint where;
565 	uint32 buttons;
566 	if (window->Lock()) {
567 		if (startIndex != -1) {
568 			be_app->ObscureCursor();
569 			_SelectItem(ItemAt(startIndex), true, false);
570 		}
571 		GetMouse(&where, &buttons);
572 		window->Unlock();
573 	}
574 
575 	while (fState != MENU_STATE_CLOSED) {
576 		bigtime_t snoozeAmount = 40000;
577 		if (Window() == NULL || !window->Lock())
578 			break;
579 
580 		BMenuItem* menuItem = NULL;
581 		if (dynamic_cast<_BMCMenuBar_*>(this))
582 			menuItem = ItemAt(0);
583 		else
584 			menuItem = _HitTestItems(where, B_ORIGIN);
585 		if (_OverSubmenu(fSelected, ConvertToScreen(where))
586 			|| fState == MENU_STATE_KEY_TO_SUBMENU) {
587 			// call _Track() from the selected sub-menu when the mouse cursor
588 			// is over its window
589 			BMenu* menu = fSelected->Submenu();
590 			window->Unlock();
591 			snoozeAmount = 30000;
592 			bool wasSticky = _IsStickyMode();
593 			menu->_SetStickyMode(wasSticky);
594 			int localAction;
595 			fChosenItem = menu->_Track(&localAction);
596 
597 			// The mouse could have meen moved since the last time we
598 			// checked its position, or buttons might have been pressed.
599 			// Unfortunately our child menus don't tell
600 			// us the new position.
601 			// TODO: Maybe have a shared struct between all menus
602 			// where to store the current mouse position ?
603 			// (Or just use the BView mouse hooks)
604 			BPoint newWhere;
605 			if (window->Lock()) {
606 				GetMouse(&newWhere, &buttons);
607 				window->Unlock();
608 			}
609 
610 			// This code is needed to make menus
611 			// that are children of BMenuFields "sticky" (see ticket #953)
612 			if (localAction == MENU_STATE_CLOSED) {
613 				if (fExtraRect != NULL && fExtraRect->Contains(where)
614 					// 9 = 3 pixels ^ 2 (since point_distance() returns the
615 					// square of the distance)
616 					&& point_distance(newWhere, where) < 9) {
617 					_SetStickyMode(true);
618 					fExtraRect = NULL;
619 				} else
620 					fState = MENU_STATE_CLOSED;
621 			}
622 			if (!window->Lock())
623 				break;
624 		} else if (menuItem != NULL) {
625 			if (menuItem->Submenu() != NULL && menuItem != fSelected) {
626 				if (menuItem->Submenu()->Window() == NULL) {
627 					// open the menu if it's not opened yet
628 					_SelectItem(menuItem);
629 				} else {
630 					// Menu was already opened, close it and bail
631 					_SelectItem(NULL);
632 					fState = MENU_STATE_CLOSED;
633 					fChosenItem = NULL;
634 				}
635 			} else {
636 				// No submenu, just select the item
637 				_SelectItem(menuItem);
638 			}
639 		} else if (menuItem == NULL && fSelected != NULL
640 			&& !_IsStickyMode() && Bounds().Contains(where)) {
641 			_SelectItem(NULL);
642 			fState = MENU_STATE_TRACKING;
643 		}
644 
645 		window->Unlock();
646 
647 		if (fState != MENU_STATE_CLOSED) {
648 			// If user doesn't move the mouse, loop here,
649 			// so we don't interfere with keyboard menu navigation
650 			BPoint newLocation = where;
651 			uint32 newButtons = buttons;
652 			do {
653 				snooze(snoozeAmount);
654 				if (!LockLooper())
655 					break;
656 				GetMouse(&newLocation, &newButtons, true);
657 				UnlockLooper();
658 			} while (newLocation == where && newButtons == buttons
659 				&& fState == MENU_STATE_TRACKING);
660 
661 			where = newLocation;
662 			buttons = newButtons;
663 
664 			if (buttons != 0 && _IsStickyMode()) {
665 				if (menuItem == NULL
666 					|| (menuItem->Submenu() && menuItem->Submenu()->Window())) {
667 					// clicked outside menu bar or on item with already
668 					// open sub menu
669 					fState = MENU_STATE_CLOSED;
670 				} else
671 					_SetStickyMode(false);
672 			} else if (buttons == 0 && !_IsStickyMode()) {
673 				if ((fSelected != NULL && fSelected->Submenu() == NULL)
674 					|| menuItem == NULL) {
675 					fChosenItem = fSelected;
676 					fState = MENU_STATE_CLOSED;
677 				} else
678 					_SetStickyMode(true);
679 			}
680 		}
681 	}
682 
683 	if (window->Lock()) {
684 		if (fSelected != NULL)
685 			_SelectItem(NULL);
686 
687 		if (fChosenItem != NULL)
688 			fChosenItem->Invoke();
689 		_RestoreFocus();
690 		window->Unlock();
691 	}
692 
693 	if (_IsStickyMode())
694 		_SetStickyMode(false);
695 
696 	_DeleteMenuWindow();
697 
698 	if (action != NULL)
699 		*action = fState;
700 
701 	return fChosenItem;
702 
703 }
704 
705 
706 void
707 BMenuBar::_StealFocus()
708 {
709 	// We already stole the focus, don't do anything
710 	if (fPrevFocusToken != -1)
711 		return;
712 
713 	BWindow* window = Window();
714 	if (window != NULL && window->Lock()) {
715 		BView* focus = window->CurrentFocus();
716 		if (focus != NULL && focus != this)
717 			fPrevFocusToken = _get_object_token_(focus);
718 		MakeFocus();
719 		window->Unlock();
720 	}
721 }
722 
723 
724 void
725 BMenuBar::_RestoreFocus()
726 {
727 	BWindow* window = Window();
728 	if (window != NULL && window->Lock()) {
729 		BHandler* handler = NULL;
730 		if (fPrevFocusToken != -1
731 			&& gDefaultTokens.GetToken(fPrevFocusToken, B_HANDLER_TOKEN,
732 				(void**)&handler) == B_OK) {
733 			BView* view = dynamic_cast<BView*>(handler);
734 			if (view != NULL && view->Window() == window)
735 				view->MakeFocus();
736 
737 		} else if (IsFocus())
738 			MakeFocus(false);
739 
740 		fPrevFocusToken = -1;
741 		window->Unlock();
742 	}
743 }
744 
745 
746 void
747 BMenuBar::_InitData(menu_layout layout)
748 {
749 	fLastBounds = new BRect(Bounds());
750 	SetItemMargins(8, 2, 8, 2);
751 	_SetIgnoreHidden(true);
752 }
753