xref: /haiku/src/kits/interface/MenuBar.cpp (revision cf2f912782a453abaad723df61bc380e137e7493)
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
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 
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_LAYOUT_INVALIDATED:
442 		{
443 			perform_data_layout_invalidated* data
444 				= (perform_data_layout_invalidated*)_data;
445 			BMenuBar::LayoutInvalidated(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",
504 		B_DISPLAY_PRIORITY, 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 	fState = MENU_STATE_TRACKING;
561 
562 	BPoint where;
563 	uint32 buttons;
564 	if (LockLooper()) {
565 		if (startIndex != -1) {
566 			be_app->ObscureCursor();
567 			_SelectItem(ItemAt(startIndex), true, false);
568 		}
569 		GetMouse(&where, &buttons);
570 		UnlockLooper();
571 	}
572 
573 	while (fState != MENU_STATE_CLOSED) {
574 		bigtime_t snoozeAmount = 40000;
575 		if (!LockLooper())
576 			break;
577 
578 		BMenuItem* menuItem = NULL;
579 		if (dynamic_cast<_BMCMenuBar_*>(this))
580 			menuItem = ItemAt(0);
581 		else
582 			menuItem = _HitTestItems(where, B_ORIGIN);
583 		if (_OverSubmenu(fSelected, ConvertToScreen(where))
584 			|| fState == MENU_STATE_KEY_TO_SUBMENU) {
585 			// call _Track() from the selected sub-menu when the mouse cursor
586 			// is over its window
587 			BMenu* menu = fSelected->Submenu();
588 			UnlockLooper();
589 			snoozeAmount = 30000;
590 			bool wasSticky = _IsStickyMode();
591 			menu->_SetStickyMode(wasSticky);
592 			int localAction;
593 			fChosenItem = menu->_Track(&localAction);
594 
595 			// The mouse could have meen moved since the last time we
596 			// checked its position, or buttons might have been pressed.
597 			// Unfortunately our child menus don't tell
598 			// us the new position.
599 			// TODO: Maybe have a shared struct between all menus
600 			// where to store the current mouse position ?
601 			// (Or just use the BView mouse hooks)
602 			BPoint newWhere;
603 			if (LockLooper()) {
604 				GetMouse(&newWhere, &buttons);
605 				UnlockLooper();
606 			}
607 
608 			// This code is needed to make menus
609 			// that are children of BMenuFields "sticky" (see ticket #953)
610 			if (localAction == MENU_STATE_CLOSED) {
611 				if (fExtraRect != NULL && fExtraRect->Contains(where)
612 					// 9 = 3 pixels ^ 2 (since point_distance() returns the
613 					// square of the distance)
614 					&& point_distance(newWhere, where) < 9) {
615 					_SetStickyMode(true);
616 					fExtraRect = NULL;
617 				} else
618 					fState = MENU_STATE_CLOSED;
619 			}
620 			if (!LockLooper())
621 				break;
622 		} else if (menuItem != NULL) {
623 			if (menuItem->Submenu() != NULL && menuItem != fSelected) {
624 				if (menuItem->Submenu()->Window() == NULL) {
625 					// open the menu if it's not opened yet
626 					_SelectItem(menuItem);
627 				} else {
628 					// Menu was already opened, close it and bail
629 					_SelectItem(NULL);
630 					fState = MENU_STATE_CLOSED;
631 					fChosenItem = NULL;
632 				}
633 			} else {
634 				// No submenu, just select the item
635 				_SelectItem(menuItem);
636 			}
637 		} else if (menuItem == NULL && fSelected != NULL
638 			&& !_IsStickyMode() && Bounds().Contains(where)) {
639 			_SelectItem(NULL);
640 			fState = MENU_STATE_TRACKING;
641 		}
642 
643 		UnlockLooper();
644 
645 		if (fState != MENU_STATE_CLOSED) {
646 			// If user doesn't move the mouse, loop here,
647 			// so we don't interfere with keyboard menu navigation
648 			BPoint newLocation = where;
649 			uint32 newButtons = buttons;
650 			do {
651 				snooze(snoozeAmount);
652 				if (!LockLooper())
653 					break;
654 				GetMouse(&newLocation, &newButtons, true);
655 				UnlockLooper();
656 			} while (newLocation == where && newButtons == buttons
657 				&& fState == MENU_STATE_TRACKING);
658 
659 			where = newLocation;
660 			buttons = newButtons;
661 
662 			if (buttons != 0 && _IsStickyMode()) {
663 				if (menuItem == NULL
664 					|| (menuItem->Submenu() && menuItem->Submenu()->Window())) {
665 					// clicked outside menu bar or on item with already
666 					// open sub menu
667 					fState = MENU_STATE_CLOSED;
668 				} else
669 					_SetStickyMode(false);
670 			} else if (buttons == 0 && !_IsStickyMode()) {
671 				if ((fSelected != NULL && fSelected->Submenu() == NULL)
672 					|| menuItem == NULL) {
673 					fChosenItem = fSelected;
674 					fState = MENU_STATE_CLOSED;
675 				} else
676 					_SetStickyMode(true);
677 			}
678 		}
679 	}
680 
681 	if (LockLooper()) {
682 		if (fSelected != NULL)
683 			_SelectItem(NULL);
684 
685 		if (fChosenItem != NULL)
686 			fChosenItem->Invoke();
687 		_RestoreFocus();
688 		UnlockLooper();
689 	}
690 
691 	if (_IsStickyMode())
692 		_SetStickyMode(false);
693 
694 	_DeleteMenuWindow();
695 
696 	if (action != NULL)
697 		*action = fState;
698 
699 	return fChosenItem;
700 }
701 
702 
703 void
704 BMenuBar::_StealFocus()
705 {
706 	// We already stole the focus, don't do anything
707 	if (fPrevFocusToken != -1)
708 		return;
709 
710 	BWindow* window = Window();
711 	if (window != NULL && window->Lock()) {
712 		BView* focus = window->CurrentFocus();
713 		if (focus != NULL && focus != this)
714 			fPrevFocusToken = _get_object_token_(focus);
715 		MakeFocus();
716 		window->Unlock();
717 	}
718 }
719 
720 
721 void
722 BMenuBar::_RestoreFocus()
723 {
724 	BWindow* window = Window();
725 	if (window != NULL && window->Lock()) {
726 		BHandler* handler = NULL;
727 		if (fPrevFocusToken != -1
728 			&& gDefaultTokens.GetToken(fPrevFocusToken, B_HANDLER_TOKEN,
729 				(void**)&handler) == B_OK) {
730 			BView* view = dynamic_cast<BView*>(handler);
731 			if (view != NULL && view->Window() == window)
732 				view->MakeFocus();
733 
734 		} else if (IsFocus())
735 			MakeFocus(false);
736 
737 		fPrevFocusToken = -1;
738 		window->Unlock();
739 	}
740 }
741 
742 
743 void
744 BMenuBar::_InitData(menu_layout layout)
745 {
746 	fLastBounds = new BRect(Bounds());
747 	SetItemMargins(8, 2, 8, 2);
748 	_SetIgnoreHidden(true);
749 }
750