xref: /haiku/src/kits/interface/MenuBar.cpp (revision 302f62604763c95777d6d04cca456e876f471c4f)
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 - 2, 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 	menuBar->SetStickyMode(data.sticky);
386 
387 	int32 action;
388 	menuBar->Track(&action, data.menuIndex, data.showMenu);
389 
390 	menuBar->fTracking = false;
391 
392 	// Sends a _MENUS_DONE_ message to the BWindow.
393 	// Weird: There is a _MENUS_DONE_ message but not a
394 	// _MENUS_BEGINNING_ message, in fact the MenusBeginning()
395 	// hook function is called directly.
396 	BWindow *window = menuBar->Window();
397 	window->PostMessage(_MENUS_DONE_);
398 
399 	_set_menu_sem_(window, B_BAD_SEM_ID);
400 	delete_sem(menuBar->fMenuSem);
401 	menuBar->fMenuSem = B_BAD_SEM_ID;
402 
403 	return 0;
404 }
405 
406 
407 BMenuItem *
408 BMenuBar::Track(int32 *action, int32 startIndex, bool showMenu)
409 {
410 	// TODO: Cleanup, merge some "if" blocks if possible
411 	fChosenItem = NULL;
412 
413 	BWindow *window = Window();
414 	fState = MENU_STATE_TRACKING;
415 
416 	if (startIndex != -1) {
417 		be_app->ObscureCursor();
418 		window->Lock();
419 		SelectItem(ItemAt(startIndex), 0, true);
420 		window->Unlock();
421 	}
422 	while (true) {
423 		bigtime_t snoozeAmount = 40000;
424 		bool locked = window->Lock();//WithTimeout(200000)
425 		if (!locked)
426 			break;
427 
428 		BPoint where;
429 		ulong buttons;
430 		GetMouse(&where, &buttons, true);
431 
432 		window->UpdateIfNeeded();
433 		BMenuItem *menuItem = HitTestItems(where, B_ORIGIN);
434 		if (menuItem != NULL) {
435 			// Select item if:
436 			// - no previous selection
437 			// - nonsticky mode and different selection,
438 			// - clicked in sticky mode
439 			if (fSelected == NULL
440 				|| (!IsStickyMode() && menuItem != fSelected)
441 				|| (buttons != 0 && IsStickyMode())) {
442 				if (menuItem->Submenu() != NULL) {
443 					if (menuItem->Submenu()->Window() == NULL) {
444 						// open the menu if it's not opened yet
445 						SelectItem(menuItem);
446 						if (IsStickyMode())
447 							SetStickyMode(false);
448 					} else {
449 						// Menu was already opened, close it and bail
450 						SelectItem(NULL);
451 						fState = MENU_STATE_CLOSED;
452 						fChosenItem = NULL;
453 					}
454 				} else {
455 					// No submenu, just select the item
456 					SelectItem(menuItem);
457 				}
458 			}
459 		}
460 
461 		if (fSelected != NULL && OverSubmenu(fSelected, ConvertToScreen(where))) {
462 			// call _track() from the selected sub-menu when the mouse cursor
463 			// is over its window
464 			BMenu *menu = fSelected->Submenu();
465 			if (menu != NULL) {
466 				window->Unlock();
467 				locked = false;
468 				snoozeAmount = 30000;
469 				if (IsStickyMode())
470 					menu->SetStickyMode(true);
471 				int localAction;
472 				fChosenItem = menu->_track(&localAction, system_time());
473 				//menu->Window()->Activate();
474 				if (localAction == MENU_STATE_CLOSED)
475 					fState = MENU_STATE_CLOSED;
476 			}
477 		} else if (menuItem == NULL && !IsStickyMode()
478 				&& fState != MENU_STATE_TRACKING_SUBMENU) {
479 			SelectItem(NULL);
480 			fState = MENU_STATE_TRACKING;
481 		}
482 
483 		if (locked)
484 			window->Unlock();
485 
486 		if (fState == MENU_STATE_CLOSED
487 			|| (buttons != 0 && IsStickyMode() && menuItem == NULL))
488 			break;
489 		else if (buttons == 0 && !IsStickyMode()) {
490 			if ((fSelected != NULL && fSelected->Submenu() == NULL) || menuItem == NULL) {
491 				fChosenItem = fSelected;
492 				break;
493 			} else
494 				SetStickyMode(true);
495 		}
496 
497 		if (snoozeAmount > 0)
498 			snooze(snoozeAmount);
499 	}
500 
501 	if (window->Lock()) {
502 		if (fSelected != NULL)
503 			SelectItem(NULL);
504 		if (fChosenItem != NULL)
505 			fChosenItem->Invoke();
506 		RestoreFocus();
507 		window->Unlock();
508 	}
509 
510 	if (IsStickyMode())
511 		SetStickyMode(false);
512 
513 	DeleteMenuWindow();
514 
515 	if (action != NULL)
516 		*action = fState;
517 
518 	return fChosenItem;
519 }
520 
521 
522 void
523 BMenuBar::StealFocus()
524 {
525 	// We already stole the focus, don't do anything
526 	if (fPrevFocusToken != -1)
527 		return;
528 
529 	BWindow *window = Window();
530 	if (window != NULL && window->Lock()) {
531 		BView *focus = window->CurrentFocus();
532 		if (focus != NULL && focus != this)
533 			fPrevFocusToken = _get_object_token_(focus);
534 		MakeFocus();
535 		window->Unlock();
536 	}
537 }
538 
539 
540 void
541 BMenuBar::RestoreFocus()
542 {
543 	BWindow *window = Window();
544 	if (window != NULL && window->Lock()) {
545 		BHandler *handler = NULL;
546 		if (fPrevFocusToken != -1
547 			&& gDefaultTokens.GetToken(fPrevFocusToken, B_HANDLER_TOKEN, (void **)&handler) == B_OK) {
548 			BView *view = dynamic_cast<BView *>(handler);
549 			if (view != NULL && view->Window() == window)
550 				view->MakeFocus();
551 
552 		} else if (IsFocus())
553 			MakeFocus(false);
554 
555 		fPrevFocusToken = -1;
556 		window->Unlock();
557 	}
558 }
559 
560 
561 void
562 BMenuBar::InitData(menu_layout layout)
563 {
564 	fLastBounds = new BRect(Bounds());
565 	SetItemMargins(8, 2, 8, 2);
566 	SetIgnoreHidden(true);
567 }
568