xref: /haiku/src/kits/interface/MenuBar.cpp (revision 1acbe440b8dd798953bec31d18ee589aa3f71b73)
1 /*
2  * Copyright 2001-2006, Haiku, Inc.
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  */
9 
10 
11 #include <MenuBar.h>
12 
13 #include <Application.h>
14 #include <Autolock.h>
15 #include <LayoutUtils.h>
16 #include <MenuItem.h>
17 #include <Window.h>
18 
19 #include <AppMisc.h>
20 #include <MenuPrivate.h>
21 #include <TokenSpace.h>
22 
23 using BPrivate::gDefaultTokens;
24 
25 
26 struct menubar_data {
27 	BMenuBar *menuBar;
28 	int32 menuIndex;
29 
30 	bool sticky;
31 	bool showMenu;
32 
33 	bool useRect;
34 	BRect rect;
35 };
36 
37 
38 BMenuBar::BMenuBar(BRect frame, const char *title, uint32 resizeMask,
39 		menu_layout layout, bool resizeToFit)
40 	: BMenu(frame, title, resizeMask, B_WILL_DRAW | B_FRAME_EVENTS, layout,
41 		resizeToFit),
42 	fBorder(B_BORDER_FRAME),
43 	fTrackingPID(-1),
44 	fPrevFocusToken(-1),
45 	fMenuSem(-1),
46 	fLastBounds(NULL),
47 	fTracking(false)
48 {
49 	InitData(layout);
50 }
51 
52 
53 BMenuBar::BMenuBar(BMessage *data)
54 	: BMenu(data),
55 	fBorder(B_BORDER_FRAME),
56 	fTrackingPID(-1),
57 	fPrevFocusToken(-1),
58 	fMenuSem(-1),
59 	fLastBounds(NULL),
60 	fTracking(false)
61 {
62 	int32 border;
63 
64 	if (data->FindInt32("_border", &border) == B_OK)
65 		SetBorder((menu_bar_border)border);
66 
67 	menu_layout layout = B_ITEMS_IN_COLUMN;
68 	data->FindInt32("_layout", (int32 *)&layout);
69 
70 	InitData(layout);
71 }
72 
73 
74 BMenuBar::~BMenuBar()
75 {
76 	if (fTracking) {
77 		status_t dummy;
78 		wait_for_thread(fTrackingPID, &dummy);
79 	}
80 
81 	delete fLastBounds;
82 }
83 
84 
85 BArchivable *
86 BMenuBar::Instantiate(BMessage *data)
87 {
88 	if (validate_instantiation(data, "BMenuBar"))
89 		return new BMenuBar(data);
90 
91 	return NULL;
92 }
93 
94 
95 status_t
96 BMenuBar::Archive(BMessage *data, bool deep) const
97 {
98 	status_t err = BMenu::Archive(data, deep);
99 
100 	if (err < B_OK)
101 		return err;
102 
103 	if (Border() != B_BORDER_FRAME)
104 		err = data->AddInt32("_border", Border());
105 
106 	return err;
107 }
108 
109 
110 void
111 BMenuBar::SetBorder(menu_bar_border border)
112 {
113 	fBorder = border;
114 }
115 
116 
117 menu_bar_border
118 BMenuBar::Border() const
119 {
120 	return fBorder;
121 }
122 
123 
124 void
125 BMenuBar::Draw(BRect updateRect)
126 {
127 	if (RelayoutIfNeeded()) {
128 		Invalidate();
129 		return;
130 	}
131 
132 	// TODO: implement additional border styles
133 	rgb_color color = HighColor();
134 
135 	BRect bounds(Bounds());
136 	// Restore the background of the previously selected menuitem
137 	DrawBackground(bounds & updateRect);
138 
139 	SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), B_LIGHTEN_2_TINT));
140 	StrokeLine(BPoint(0.0f, bounds.bottom - 2.0f), BPoint(0.0f, 0.0f));
141 	StrokeLine(BPoint(bounds.right, 0.0f));
142 
143 	SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), B_DARKEN_1_TINT));
144 	StrokeLine(BPoint(1.0f, bounds.bottom - 1.0f),
145 		BPoint(bounds.right, bounds.bottom - 1.0f));
146 
147 	SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), B_DARKEN_2_TINT));
148 	StrokeLine(BPoint(0.0f, bounds.bottom), BPoint(bounds.right, bounds.bottom));
149 	StrokeLine(BPoint(bounds.right, 0.0f), BPoint(bounds.right, bounds.bottom));
150 
151 	SetHighColor(color);
152 		// revert to previous used color (cheap PushState()/PopState())
153 
154 	DrawItems(updateRect);
155 }
156 
157 
158 void
159 BMenuBar::AttachedToWindow()
160 {
161 	Install(Window());
162 	Window()->SetKeyMenuBar(this);
163 
164 	BMenu::AttachedToWindow();
165 
166 	*fLastBounds = Bounds();
167 }
168 
169 
170 void
171 BMenuBar::DetachedFromWindow()
172 {
173 	BMenu::DetachedFromWindow();
174 }
175 
176 
177 void
178 BMenuBar::MessageReceived(BMessage *msg)
179 {
180 	BMenu::MessageReceived(msg);
181 }
182 
183 
184 void
185 BMenuBar::MouseDown(BPoint where)
186 {
187 	if (fTracking)
188 		return;
189 
190 	BWindow *window = Window();
191 	if (!window->IsActive() || !window->IsFront()) {
192 		window->Activate();
193 		window->UpdateIfNeeded();
194 	}
195 
196 	StartMenuBar(-1, false, false);
197 }
198 
199 
200 void
201 BMenuBar::WindowActivated(bool state)
202 {
203 	BView::WindowActivated(state);
204 }
205 
206 
207 void
208 BMenuBar::MouseUp(BPoint where)
209 {
210 	BView::MouseUp(where);
211 }
212 
213 
214 void
215 BMenuBar::FrameMoved(BPoint newPosition)
216 {
217 	BMenu::FrameMoved(newPosition);
218 }
219 
220 
221 void
222 BMenuBar::FrameResized(float newWidth, float newHeight)
223 {
224 	BRect bounds(Bounds());
225 	BRect rect(fLastBounds->right - 12, fLastBounds->top, bounds.right, bounds.bottom);
226 	fLastBounds->Set(0, 0, newWidth, newHeight);
227 
228 	Invalidate(rect);
229 
230 	BMenu::FrameResized(newWidth, newHeight);
231 }
232 
233 
234 void
235 BMenuBar::Show()
236 {
237 	BView::Show();
238 }
239 
240 
241 void
242 BMenuBar::Hide()
243 {
244 	BView::Hide();
245 }
246 
247 
248 BHandler *
249 BMenuBar::ResolveSpecifier(BMessage *msg, int32 index, BMessage *specifier, int32 form, const char *property)
250 {
251 	return BMenu::ResolveSpecifier(msg, index, specifier, form, property);
252 }
253 
254 
255 status_t
256 BMenuBar::GetSupportedSuites(BMessage *data)
257 {
258 	return BMenu::GetSupportedSuites(data);
259 }
260 
261 
262 void
263 BMenuBar::ResizeToPreferred()
264 {
265 	BMenu::ResizeToPreferred();
266 }
267 
268 
269 void
270 BMenuBar::GetPreferredSize(float *width, float *height)
271 {
272 	BMenu::GetPreferredSize(width, height);
273 }
274 
275 
276 void
277 BMenuBar::MakeFocus(bool state)
278 {
279 	BMenu::MakeFocus(state);
280 }
281 
282 
283 void
284 BMenuBar::AllAttached()
285 {
286 	BMenu::AllAttached();
287 }
288 
289 
290 void
291 BMenuBar::AllDetached()
292 {
293 	BMenu::AllDetached();
294 }
295 
296 
297 status_t
298 BMenuBar::Perform(perform_code d, void *arg)
299 {
300 	return BMenu::Perform(d, arg);
301 }
302 
303 
304 BSize
305 BMenuBar::MaxSize()
306 {
307 	// TODO: cache the result
308 	float width, height;
309 	GetPreferredSize(&width, &height);
310 
311 	return BLayoutUtils::ComposeSize(ExplicitMaxSize(),
312 		BSize(B_SIZE_UNLIMITED, height));
313 }
314 
315 
316 // #pragma mark -
317 
318 
319 void BMenuBar::_ReservedMenuBar1() {}
320 void BMenuBar::_ReservedMenuBar2() {}
321 void BMenuBar::_ReservedMenuBar3() {}
322 void BMenuBar::_ReservedMenuBar4() {}
323 
324 
325 BMenuBar &
326 BMenuBar::operator=(const BMenuBar &)
327 {
328 	return *this;
329 }
330 
331 
332 void
333 BMenuBar::StartMenuBar(int32 menuIndex, bool sticky, bool showMenu, BRect *specialRect)
334 {
335 	if (fTracking)
336 		return;
337 
338 	BWindow *window = Window();
339 	if (window == NULL)
340 		debugger("MenuBar must be added to a window before it can be used.");
341 
342 	BAutolock lock(window);
343 	if (!lock.IsLocked())
344 		return;
345 
346 	fPrevFocusToken = -1;
347 	fTracking = true;
348 
349 	window->MenusBeginning();
350 
351 	fMenuSem = create_sem(0, "window close sem");
352 	_set_menu_sem_(window, fMenuSem);
353 
354 	fTrackingPID = spawn_thread(TrackTask, "menu_tracking", B_DISPLAY_PRIORITY, NULL);
355 	if (fTrackingPID >= 0) {
356 		menubar_data data;
357 		data.menuBar = this;
358 		data.menuIndex = menuIndex;
359 		data.sticky = sticky;
360 		data.showMenu = showMenu;
361 		data.useRect = specialRect != NULL;
362 		if (data.useRect)
363 			data.rect = *specialRect;
364 
365 		resume_thread(fTrackingPID);
366 		send_data(fTrackingPID, 0, &data, sizeof(data));
367 
368 	} else {
369 		fTracking = false;
370 		_set_menu_sem_(window, B_NO_MORE_SEMS);
371 		delete_sem(fMenuSem);
372 	}
373 }
374 
375 
376 long
377 BMenuBar::TrackTask(void *arg)
378 {
379 	menubar_data data;
380 	thread_id id;
381 
382 	receive_data(&id, &data, sizeof(data));
383 
384 	BMenuBar *menuBar = data.menuBar;
385 	if (data.useRect)
386 		menuBar->fExtraRect = &data.rect;
387 	menuBar->SetStickyMode(data.sticky);
388 
389 	int32 action;
390 	menuBar->Track(&action, data.menuIndex, data.showMenu);
391 
392 	menuBar->fTracking = false;
393 	menuBar->fExtraRect = NULL;
394 
395 	// Sends a _MENUS_DONE_ message to the BWindow.
396 	// Weird: There is a _MENUS_DONE_ message but not a
397 	// _MENUS_BEGINNING_ message, in fact the MenusBeginning()
398 	// hook function is called directly.
399 	BWindow *window = menuBar->Window();
400 	window->PostMessage(_MENUS_DONE_);
401 
402 	_set_menu_sem_(window, B_BAD_SEM_ID);
403 	delete_sem(menuBar->fMenuSem);
404 	menuBar->fMenuSem = B_BAD_SEM_ID;
405 
406 	return 0;
407 }
408 
409 
410 BMenuItem *
411 BMenuBar::Track(int32 *action, int32 startIndex, bool showMenu)
412 {
413 	// TODO: Cleanup, merge some "if" blocks if possible
414 	fChosenItem = NULL;
415 
416 	BWindow *window = Window();
417 	fState = MENU_STATE_TRACKING;
418 
419 	if (startIndex != -1) {
420 		be_app->ObscureCursor();
421 		if (window->Lock()) {
422 			_SelectItem(ItemAt(startIndex), true, true);
423 			window->Unlock();
424 		}
425 	}
426 
427 	while (true) {
428 		bigtime_t snoozeAmount = 40000;
429 		bool locked = window->Lock();//WithTimeout(200000)
430 		if (!locked)
431 			break;
432 
433 		BPoint where;
434 		ulong buttons;
435 		GetMouse(&where, &buttons, true);
436 
437 		BMenuItem *menuItem = HitTestItems(where, B_ORIGIN);
438 		if (menuItem != NULL) {
439 			// Select item if:
440 			// - no previous selection
441 			// - nonsticky mode and different selection,
442 			// - clicked in sticky mode
443 			if (fSelected == NULL
444 				|| (!IsStickyMode() && menuItem != fSelected)
445 				|| (buttons != 0 && IsStickyMode())) {
446 				if (menuItem->Submenu() != NULL) {
447 					if (menuItem->Submenu()->Window() == NULL) {
448 						// open the menu if it's not opened yet
449 						_SelectItem(menuItem);
450 						if (IsStickyMode())
451 							SetStickyMode(false);
452 					} else {
453 						// Menu was already opened, close it and bail
454 						_SelectItem(NULL);
455 						fState = MENU_STATE_CLOSED;
456 						fChosenItem = NULL;
457 					}
458 				} else {
459 					// No submenu, just select the item
460 					_SelectItem(menuItem);
461 				}
462 			}
463 		}
464 
465 		if (OverSubmenu(fSelected, ConvertToScreen(where))) {
466 			// call _track() from the selected sub-menu when the mouse cursor
467 			// is over its window
468 			BMenu *menu = fSelected->Submenu();
469 			window->Unlock();
470 			locked = false;
471 			snoozeAmount = 30000;
472 			bool wasSticky = IsStickyMode();
473 			if (wasSticky)
474 				menu->SetStickyMode(true);
475 			int localAction;
476 			fChosenItem = menu->_track(&localAction);
477 			if (menu->State(NULL) == MENU_STATE_TRACKING && menu->IsStickyMode())
478 				menu->SetStickyMode(false);
479 
480 			// check if the user started holding down a mouse button in a submenu
481 			if (wasSticky && !IsStickyMode())
482 				buttons = 1;
483 					// buttons must have been pressed in the meantime
484 
485 			if (localAction == MENU_STATE_CLOSED) {
486 				if (fExtraRect != NULL && fExtraRect->Contains(where)) {
487 					SetStickyMode(true);
488 					fExtraRect = NULL;
489 				} else
490 					fState = MENU_STATE_CLOSED;
491 			}
492 
493 		} else if (menuItem == NULL && fSelected != NULL
494 			&& !IsStickyMode() /*&& Bounds().Contains(where)*/ && fState != MENU_STATE_TRACKING_SUBMENU) {
495 			_SelectItem(NULL);
496 			fState = MENU_STATE_TRACKING;
497 		}
498 
499 		if (locked)
500 			window->Unlock();
501 
502 		if (fState == MENU_STATE_CLOSED
503 			|| (buttons != 0 && IsStickyMode() && menuItem == NULL))
504 			break;
505 		else if (buttons == 0 && !IsStickyMode()) {
506 			if ((fSelected != NULL && fSelected->Submenu() == NULL) || menuItem == NULL) {
507 				fChosenItem = fSelected;
508 				break;
509 			} else
510 				SetStickyMode(true);
511 		}
512 
513 		if (snoozeAmount > 0)
514 			snooze(snoozeAmount);
515 	}
516 
517 	if (window->Lock()) {
518 		if (fSelected != NULL)
519 			_SelectItem(NULL);
520 
521 		if (fChosenItem != NULL)
522 			fChosenItem->Invoke();
523 		RestoreFocus();
524 		window->Unlock();
525 	}
526 
527 	if (IsStickyMode())
528 		SetStickyMode(false);
529 
530 	DeleteMenuWindow();
531 
532 	if (action != NULL)
533 		*action = fState;
534 
535 	return fChosenItem;
536 }
537 
538 
539 void
540 BMenuBar::StealFocus()
541 {
542 	// We already stole the focus, don't do anything
543 	if (fPrevFocusToken != -1)
544 		return;
545 
546 	BWindow *window = Window();
547 	if (window != NULL && window->Lock()) {
548 		BView *focus = window->CurrentFocus();
549 		if (focus != NULL && focus != this)
550 			fPrevFocusToken = _get_object_token_(focus);
551 		MakeFocus();
552 		window->Unlock();
553 	}
554 }
555 
556 
557 void
558 BMenuBar::RestoreFocus()
559 {
560 	BWindow *window = Window();
561 	if (window != NULL && window->Lock()) {
562 		BHandler *handler = NULL;
563 		if (fPrevFocusToken != -1
564 			&& gDefaultTokens.GetToken(fPrevFocusToken, B_HANDLER_TOKEN, (void **)&handler) == B_OK) {
565 			BView *view = dynamic_cast<BView *>(handler);
566 			if (view != NULL && view->Window() == window)
567 				view->MakeFocus();
568 
569 		} else if (IsFocus())
570 			MakeFocus(false);
571 
572 		fPrevFocusToken = -1;
573 		window->Unlock();
574 	}
575 }
576 
577 
578 void
579 BMenuBar::InitData(menu_layout layout)
580 {
581 	fLastBounds = new BRect(Bounds());
582 	SetItemMargins(8, 2, 8, 2);
583 	SetIgnoreHidden(true);
584 }
585