xref: /haiku/src/kits/interface/MenuBar.cpp (revision 4f00613311d0bd6b70fa82ce19931c41f071ea4e)
1  //------------------------------------------------------------------------------
2 //	Copyright (c) 2001-2005, Haiku, Inc.
3 //
4 //	Permission is hereby granted, free of charge, to any person obtaining a
5 //	copy of this software and associated documentation files (the "Software"),
6 //	to deal in the Software without restriction, including without limitation
7 //	the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 //	and/or sell copies of the Software, and to permit persons to whom the
9 //	Software is furnished to do so, subject to the following conditions:
10 //
11 //	The above copyright notice and this permission notice shall be included in
12 //	all copies or substantial portions of the Software.
13 //
14 //	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 //	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 //	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 //	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 //	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19 //	FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20 //	DEALINGS IN THE SOFTWARE.
21 //
22 //	File Name:		Menubar.cpp
23 //	Authors:		Marc Flerackers (mflerackers@androme.be)
24 //					Stefano Ceccherini (burton666@libero.it)
25 //	Description:	BMenuBar is a menu that's at the root of a menu hierarchy.
26 //------------------------------------------------------------------------------
27 // TODO: Finish this class
28 
29 #include <Application.h>
30 #include <Autolock.h>
31 #include <MenuBar.h>
32 #include <MenuItem.h>
33 #include <Window.h>
34 
35 #include <AppMisc.h>
36 #include <MenuPrivate.h>
37 #include <TokenSpace.h>
38 
39 struct menubar_data
40 {
41 	BMenuBar *menuBar;
42 	int32 menuIndex;
43 
44 	bool sticky;
45 	bool showMenu;
46 
47 	bool useRect;
48 	BRect rect;
49 };
50 
51 
52 BMenuBar::BMenuBar(BRect frame, const char *title, uint32 resizeMask,
53 				   menu_layout layout, bool resizeToFit)
54 	:	BMenu(frame, title, resizeMask,
55 			B_WILL_DRAW | B_FRAME_EVENTS, layout, resizeToFit),
56 		fBorder(B_BORDER_FRAME),
57 		fTrackingPID(-1),
58 		fPrevFocusToken(-1),
59 		fMenuSem(-1),
60 		fLastBounds(NULL),
61 		fTracking(false)
62 {
63 	InitData(layout);
64 }
65 
66 
67 BMenuBar::BMenuBar(BMessage *data)
68 	:	BMenu(data),
69 		fBorder(B_BORDER_FRAME),
70 		fTrackingPID(-1),
71 		fPrevFocusToken(-1),
72 		fMenuSem(-1),
73 		fLastBounds(NULL),
74 		fTracking(false)
75 {
76 	int32 border;
77 
78 	if (data->FindInt32("_border", &border) == B_OK)
79 		SetBorder((menu_bar_border)border);
80 
81 	// TODO: InitData() ??
82 }
83 
84 
85 BMenuBar::~BMenuBar()
86 {
87 	if (fTrackingPID >= 0) {
88 		status_t dummy;
89 		wait_for_thread(fTrackingPID, &dummy);
90 	}
91 
92 	delete fLastBounds;
93 }
94 
95 
96 BArchivable *
97 BMenuBar::Instantiate(BMessage *data)
98 {
99 	if (validate_instantiation(data, "BMenuBar"))
100 		return new BMenuBar(data);
101 	else
102 		return NULL;
103 }
104 
105 
106 status_t
107 BMenuBar::Archive(BMessage *data, bool deep) const
108 {
109 	status_t err = BMenu::Archive(data, deep);
110 
111 	if (err < B_OK)
112 		return err;
113 
114 	if (Border() != B_BORDER_FRAME)
115 		err = data->AddInt32("_border", Border());
116 
117 	return err;
118 }
119 
120 
121 void
122 BMenuBar::SetBorder(menu_bar_border border)
123 {
124 	fBorder = border;
125 }
126 
127 
128 menu_bar_border
129 BMenuBar::Border() const
130 {
131 	return fBorder;
132 }
133 
134 
135 void
136 BMenuBar::Draw(BRect updateRect)
137 {
138 	// TODO: implement additional border styles
139 	if (IsEnabled()) {
140 		rgb_color color = HighColor();
141 
142 		BRect bounds(Bounds());
143 		// Restore the background of the previously selected menuitem
144 		DrawBackground(bounds & updateRect);
145 
146 		SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), B_LIGHTEN_2_TINT));
147 		StrokeLine(BPoint(0.0f, bounds.bottom - 2.0f), BPoint(0.0f, 0.0f));
148 		StrokeLine(BPoint(bounds.right, 0.0f));
149 
150 		SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), B_DARKEN_1_TINT));
151 		StrokeLine(BPoint(1.0f, bounds.bottom - 1.0f),
152 			BPoint(bounds.right, bounds.bottom - 1.0f));
153 
154 		SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), B_DARKEN_2_TINT));
155 		StrokeLine(BPoint(0.0f, bounds.bottom), BPoint(bounds.right, bounds.bottom));
156 		StrokeLine(BPoint(bounds.right, 0.0f), BPoint(bounds.right, bounds.bottom));
157 
158 		SetHighColor(color);
159 			// revert to previous used color (cheap PushState()/PopState())
160 
161 		DrawItems(updateRect);
162 	} else {
163 		LayoutItems(0);
164 		Sync();
165 		Invalidate();
166 	}
167 }
168 
169 
170 void
171 BMenuBar::AttachedToWindow()
172 {
173 	Install(Window());
174 	Window()->SetKeyMenuBar(this);
175 
176 	BMenu::AttachedToWindow();
177 }
178 
179 
180 void
181 BMenuBar::DetachedFromWindow()
182 {
183 	BMenu::DetachedFromWindow();
184 }
185 
186 
187 void
188 BMenuBar::MessageReceived(BMessage *msg)
189 {
190 	BMenu::MessageReceived(msg);
191 }
192 
193 
194 void
195 BMenuBar::MouseDown(BPoint where)
196 {
197 	BWindow *window = Window();
198 	if (!window->IsActive() || !window->IsFront()) {
199 		window->Activate();
200 		window->UpdateIfNeeded();
201 	}
202 
203 	StartMenuBar(-1, false, false);
204 }
205 
206 
207 void
208 BMenuBar::WindowActivated(bool state)
209 {
210 	BView::WindowActivated(state);
211 }
212 
213 
214 void
215 BMenuBar::MouseUp(BPoint where)
216 {
217 	BView::MouseUp(where);
218 }
219 
220 
221 void
222 BMenuBar::FrameMoved(BPoint newPosition)
223 {
224 	BMenu::FrameMoved(newPosition);
225 }
226 
227 
228 void
229 BMenuBar::FrameResized(float newWidth, float newHeight)
230 {
231 	BMenu::FrameResized(newWidth, newHeight);
232 }
233 
234 
235 void
236 BMenuBar::Show()
237 {
238 	BView::Show();
239 }
240 
241 
242 void
243 BMenuBar::Hide()
244 {
245 	BView::Hide();
246 }
247 
248 
249 BHandler *
250 BMenuBar::ResolveSpecifier(BMessage *msg, int32 index,
251 									 BMessage *specifier, int32 form,
252 									 const char *property)
253 {
254 	return BMenu::ResolveSpecifier(msg, index, specifier, form, property);
255 }
256 
257 
258 status_t
259 BMenuBar::GetSupportedSuites(BMessage *data)
260 {
261 	return BMenu::GetSupportedSuites(data);
262 }
263 
264 
265 void
266 BMenuBar::ResizeToPreferred()
267 {
268 	BMenu::ResizeToPreferred();
269 }
270 
271 
272 void
273 BMenuBar::GetPreferredSize(float *width, float *height)
274 {
275 	BMenu::GetPreferredSize(width, height);
276 }
277 
278 
279 void
280 BMenuBar::MakeFocus(bool state)
281 {
282 	BMenu::MakeFocus(state);
283 }
284 
285 
286 void
287 BMenuBar::AllAttached()
288 {
289 	BMenu::AllAttached();
290 }
291 
292 
293 void
294 BMenuBar::AllDetached()
295 {
296 	BMenu::AllDetached();
297 }
298 
299 
300 status_t
301 BMenuBar::Perform(perform_code d, void *arg)
302 {
303 	return BMenu::Perform(d, arg);
304 }
305 
306 
307 void BMenuBar::_ReservedMenuBar1() {}
308 void BMenuBar::_ReservedMenuBar2() {}
309 void BMenuBar::_ReservedMenuBar3() {}
310 void BMenuBar::_ReservedMenuBar4() {}
311 
312 
313 BMenuBar &
314 BMenuBar::operator=(const BMenuBar &)
315 {
316 	return *this;
317 }
318 
319 
320 void
321 BMenuBar::StartMenuBar(int32 menuIndex, bool sticky, bool showMenu,
322 							BRect *specialRect)
323 {
324 	BWindow *window = Window();
325 	if (window == NULL)
326 		debugger("MenuBar must be added to a window before it can be used.");
327 
328 	BAutolock lock(window);
329 	if (!lock.IsLocked())
330 		return;
331 
332 	fPrevFocusToken = -1;
333 	fTracking = true;
334 
335 	window->MenusBeginning();
336 
337 	fMenuSem = create_sem(0, "window close sem");
338 	_set_menu_sem_(window, fMenuSem);
339 
340 	fTrackingPID = spawn_thread(TrackTask, "menu_tracking", B_NORMAL_PRIORITY, NULL);
341 	if (fTrackingPID >= 0) {
342 		menubar_data data;
343 		data.menuBar = this;
344 		data.menuIndex = menuIndex;
345 		data.sticky = sticky;
346 		data.showMenu = showMenu;
347 		data.useRect = specialRect != NULL;
348 		if (data.useRect)
349 			data.rect = *specialRect;
350 
351 		resume_thread(fTrackingPID);
352 		send_data(fTrackingPID, 0, &data, sizeof(data));
353 
354 	} else {
355 		_set_menu_sem_(window, B_NO_MORE_SEMS);
356 		delete_sem(fMenuSem);
357 	}
358 }
359 
360 
361 long
362 BMenuBar::TrackTask(void *arg)
363 {
364 	menubar_data data;
365 	thread_id id;
366 
367 	receive_data(&id, &data, sizeof(data));
368 
369 	BMenuBar *menuBar = data.menuBar;
370 	menuBar->SetStickyMode(data.sticky);
371 
372 	int32 action;
373 	menuBar->Track(&action, data.menuIndex, data.showMenu);
374 
375 	menuBar->fTracking = false;
376 
377 	// Sends a _MENUS_DONE_ message to the BWindow.
378 	// Weird: There is a _MENUS_DONE_ message but not a
379 	// _MENUS_BEGINNING_ message, in fact the MenusBeginning()
380 	// hook function is called directly.
381 	BWindow *window = menuBar->Window();
382 	window->PostMessage(_MENUS_DONE_);
383 
384 	_set_menu_sem_(window, B_BAD_SEM_ID);
385 	delete_sem(menuBar->fMenuSem);
386 	menuBar->fMenuSem = B_BAD_SEM_ID;
387 
388 	return 0;
389 }
390 
391 
392 BMenuItem *
393 BMenuBar::Track(int32 *action, int32 startIndex, bool showMenu)
394 {
395 	// TODO: This function is very incomplete and just partially working:
396 	// For example, it doesn't respect the "sticky mode" setting.
397 	BMenuItem *resultItem = NULL;
398 	BWindow *window = Window();
399 	int localAction = MENU_ACT_NONE;
400 
401 	while (true) {
402 		bigtime_t snoozeAmount = 30000;
403 		if (window->LockWithTimeout(200000) < B_OK)
404 			break;
405 
406 		BPoint where;
407 		ulong buttons;
408 		GetMouse(&where, &buttons);
409 
410 		BMenuItem *menuItem = HitTestItems(where, B_ORIGIN);
411 		if (menuItem != NULL && menuItem != fSelected) {
412 			// only select the item
413 			SelectItem(menuItem, -1);
414 			if (menuItem->Submenu() != NULL
415 				&& menuItem->Submenu()->Window() == NULL) {
416 				// open the menu if it's not opened yet
417 				SelectItem(menuItem);
418 			}
419 		}
420 
421 		if (fSelected != NULL) {
422 			BMenu *menu = fSelected->Submenu();
423 			if (menu != NULL) {
424 				window->Unlock();
425 				if (IsStickyPrefOn())
426 					menu->SetStickyMode(true);
427 				snoozeAmount = 0;
428 				resultItem = menu->_track(&localAction);
429 				if (window->LockWithTimeout(200000) < B_OK)
430 					break;
431 			}
432 		}
433 
434 		window->Unlock();
435 
436 		if (localAction == MENU_ACT_CLOSE)
437 			break;
438 
439 		if (snoozeAmount > 0)
440 			snooze(snoozeAmount);
441 	}
442 
443 	if (window->Lock()) {
444 		if (fSelected != NULL)
445 			SelectItem(NULL);
446 		if (resultItem != NULL)
447 			resultItem->Invoke();
448 		window->Unlock();
449 	}
450 
451 	if (action != NULL)
452 		*action = static_cast<int32>(localAction);
453 
454 	return resultItem;
455 }
456 
457 
458 void
459 BMenuBar::StealFocus()
460 {
461 	BWindow *window = Window();
462 	if (window != NULL && window->Lock()) {
463 		BView *focus = window->CurrentFocus();
464 		if (focus != NULL)
465 			fPrevFocusToken = _get_object_token_(focus);
466 		MakeFocus();
467 		window->Unlock();
468 	}
469 }
470 
471 
472 void
473 BMenuBar::RestoreFocus()
474 {
475 	BWindow *window = Window();
476 	if (window != NULL && window->Lock()) {
477 		BHandler *handler = NULL;
478 		if (BPrivate::gDefaultTokens.GetToken(fPrevFocusToken, B_HANDLER_TOKEN,
479 				(void **)&handler, NULL) == B_OK) {
480 			BView *view = dynamic_cast<BView *>(handler);
481 			if (view != NULL && view->Window() == window)
482 				view->MakeFocus();
483 		}
484 		fPrevFocusToken = -1;
485 		window->Unlock();
486 	}
487 }
488 
489 
490 void
491 BMenuBar::InitData(menu_layout layout)
492 {
493 	fLastBounds = new BRect(Bounds());
494 	SetItemMargins(8, 2, 8, 2);
495 	SetIgnoreHidden(true);
496 }
497