xref: /haiku/src/kits/interface/Menu.cpp (revision a5a3b2d9a3d95cbae71eaf371708c73a1780ac0d)
1 /*
2  * Copyright 2001-2015 Haiku, Inc. All rights reserved.
3  * Distributed under the terms of the MIT license.
4  *
5  * Authors:
6  *		Stephan Aßmus, superstippi@gmx.de
7  *		Stefano Ceccherini, stefano.ceccherini@gmail.com
8  *		Marc Flerackers, mflerackers@androme.be
9  *		Rene Gollent, anevilyak@gmail.com
10  *		John Scipione, jscipione@gmail.com
11  */
12 
13 
14 #include <Menu.h>
15 
16 #include <algorithm>
17 #include <new>
18 
19 #include <ctype.h>
20 #include <string.h>
21 
22 #include <Application.h>
23 #include <Bitmap.h>
24 #include <ControlLook.h>
25 #include <Debug.h>
26 #include <File.h>
27 #include <FindDirectory.h>
28 #include <Layout.h>
29 #include <LayoutUtils.h>
30 #include <MenuBar.h>
31 #include <MenuItem.h>
32 #include <Messenger.h>
33 #include <Path.h>
34 #include <PropertyInfo.h>
35 #include <Screen.h>
36 #include <ScrollBar.h>
37 #include <SystemCatalog.h>
38 #include <Window.h>
39 
40 #include <AppServerLink.h>
41 #include <binary_compatibility/Interface.h>
42 #include <BMCPrivate.h>
43 #include <MenuPrivate.h>
44 #include <MenuWindow.h>
45 #include <ServerProtocol.h>
46 
47 #include "utf8_functions.h"
48 
49 
50 #define USE_CACHED_MENUWINDOW 1
51 
52 using BPrivate::gSystemCatalog;
53 
54 #undef B_TRANSLATION_CONTEXT
55 #define B_TRANSLATION_CONTEXT "Menu"
56 
57 #undef B_TRANSLATE
58 #define B_TRANSLATE(str) \
59 	gSystemCatalog.GetString(B_TRANSLATE_MARK(str), "Menu")
60 
61 
62 using std::nothrow;
63 using BPrivate::BMenuWindow;
64 
65 namespace BPrivate {
66 
67 class TriggerList {
68 public:
69 	TriggerList() {}
70 	~TriggerList() {}
71 
72 	// TODO: make this work with Unicode characters!
73 
74 	bool HasTrigger(uint32 c)
75 		{ return fList.HasItem((void*)(addr_t)tolower(c)); }
76 	bool AddTrigger(uint32 c)
77 		{ return fList.AddItem((void*)(addr_t)tolower(c)); }
78 
79 private:
80 	BList	fList;
81 };
82 
83 
84 class ExtraMenuData {
85 public:
86 	menu_tracking_hook	trackingHook;
87 	void*				trackingState;
88 
89 	ExtraMenuData(menu_tracking_hook func, void* state)
90 	{
91 		trackingHook = func;
92 		trackingState = state;
93 	}
94 };
95 
96 
97 }	// namespace BPrivate
98 
99 
100 menu_info BMenu::sMenuInfo;
101 
102 uint32 BMenu::sShiftKey;
103 uint32 BMenu::sControlKey;
104 uint32 BMenu::sOptionKey;
105 uint32 BMenu::sCommandKey;
106 uint32 BMenu::sMenuKey;
107 
108 static property_info sPropList[] = {
109 	{ "Enabled", { B_GET_PROPERTY, 0 },
110 		{ B_DIRECT_SPECIFIER, 0 }, "Returns true if menu or menu item is "
111 		"enabled; false otherwise.",
112 		0, { B_BOOL_TYPE }
113 	},
114 
115 	{ "Enabled", { B_SET_PROPERTY, 0 },
116 		{ B_DIRECT_SPECIFIER, 0 }, "Enables or disables menu or menu item.",
117 		0, { B_BOOL_TYPE }
118 	},
119 
120 	{ "Label", { B_GET_PROPERTY, 0 },
121 		{ B_DIRECT_SPECIFIER, 0 }, "Returns the string label of the menu or "
122 		"menu item.",
123 		0, { B_STRING_TYPE }
124 	},
125 
126 	{ "Label", { B_SET_PROPERTY, 0 },
127 		{ B_DIRECT_SPECIFIER, 0 }, "Sets the string label of the menu or menu "
128 		"item.",
129 		0, { B_STRING_TYPE }
130 	},
131 
132 	{ "Mark", { B_GET_PROPERTY, 0 },
133 		{ B_DIRECT_SPECIFIER, 0 }, "Returns true if the menu item or the "
134 		"menu's superitem is marked; false otherwise.",
135 		0, { B_BOOL_TYPE }
136 	},
137 
138 	{ "Mark", { B_SET_PROPERTY, 0 },
139 		{ B_DIRECT_SPECIFIER, 0 }, "Marks or unmarks the menu item or the "
140 		"menu's superitem.",
141 		0, { B_BOOL_TYPE }
142 	},
143 
144 	{ "Menu", { B_CREATE_PROPERTY, 0 },
145 		{ B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 },
146 		"Adds a new menu item at the specified index with the text label "
147 		"found in \"data\" and the int32 command found in \"what\" (used as "
148 		"the what field in the BMessage sent by the item)." , 0, {},
149 		{ 	{{{"data", B_STRING_TYPE}}}
150 		}
151 	},
152 
153 	{ "Menu", { B_DELETE_PROPERTY, 0 },
154 		{ B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 },
155 		"Removes the selected menu or menus.", 0, {}
156 	},
157 
158 	{ "Menu", { },
159 		{ B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 },
160 		"Directs scripting message to the specified menu, first popping the "
161 		"current specifier off the stack.", 0, {}
162 	},
163 
164 	{ "MenuItem", { B_COUNT_PROPERTIES, 0 },
165 		{ B_DIRECT_SPECIFIER, 0 }, "Counts the number of menu items in the "
166 		"specified menu.",
167 		0, { B_INT32_TYPE }
168 	},
169 
170 	{ "MenuItem", { B_CREATE_PROPERTY, 0 },
171 		{ B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 },
172 		"Adds a new menu item at the specified index with the text label "
173 		"found in \"data\" and the int32 command found in \"what\" (used as "
174 		"the what field in the BMessage sent by the item).", 0, {},
175 		{	{ {{"data", B_STRING_TYPE },
176 			{"be:invoke_message", B_MESSAGE_TYPE},
177 			{"what", B_INT32_TYPE},
178 			{"be:target", B_MESSENGER_TYPE}} }
179 		}
180 	},
181 
182 	{ "MenuItem", { B_DELETE_PROPERTY, 0 },
183 		{ B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 },
184 		"Removes the specified menu item from its parent menu."
185 	},
186 
187 	{ "MenuItem", { B_EXECUTE_PROPERTY, 0 },
188 		{ B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 },
189 		"Invokes the specified menu item."
190 	},
191 
192 	{ "MenuItem", { },
193 		{ B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 },
194 		"Directs scripting message to the specified menu, first popping the "
195 		"current specifier off the stack."
196 	},
197 
198 	{}
199 };
200 
201 
202 // note: this is redefined to localized one in BMenu::_InitData
203 const char* BPrivate::kEmptyMenuLabel = "<empty>";
204 
205 
206 struct BMenu::LayoutData {
207 	BSize	preferred;
208 	uint32	lastResizingMode;
209 };
210 
211 
212 // #pragma mark - BMenu
213 
214 
215 BMenu::BMenu(const char* name, menu_layout layout)
216 	:
217 	BView(BRect(0, 0, 0, 0), name, 0, B_WILL_DRAW),
218 	fChosenItem(NULL),
219 	fPad(std::max(14.0f, be_plain_font->Size() + 2.0f), 2.0f, 20.0f, 0.0f),
220 	fSelected(NULL),
221 	fCachedMenuWindow(NULL),
222 	fSuper(NULL),
223 	fSuperitem(NULL),
224 	fAscent(-1.0f),
225 	fDescent(-1.0f),
226 	fFontHeight(-1.0f),
227 	fState(MENU_STATE_CLOSED),
228 	fLayout(layout),
229 	fExtraRect(NULL),
230 	fMaxContentWidth(0.0f),
231 	fInitMatrixSize(NULL),
232 	fExtraMenuData(NULL),
233 	fTrigger(0),
234 	fResizeToFit(true),
235 	fUseCachedMenuLayout(false),
236 	fEnabled(true),
237 	fDynamicName(false),
238 	fRadioMode(false),
239 	fTrackNewBounds(false),
240 	fStickyMode(false),
241 	fIgnoreHidden(true),
242 	fTriggerEnabled(true),
243 	fRedrawAfterSticky(false),
244 	fAttachAborted(false)
245 {
246 	_InitData(NULL);
247 }
248 
249 
250 BMenu::BMenu(const char* name, float width, float height)
251 	:
252 	BView(BRect(0.0f, 0.0f, 0.0f, 0.0f), name, 0, B_WILL_DRAW),
253 	fChosenItem(NULL),
254 	fSelected(NULL),
255 	fCachedMenuWindow(NULL),
256 	fSuper(NULL),
257 	fSuperitem(NULL),
258 	fAscent(-1.0f),
259 	fDescent(-1.0f),
260 	fFontHeight(-1.0f),
261 	fState(0),
262 	fLayout(B_ITEMS_IN_MATRIX),
263 	fExtraRect(NULL),
264 	fMaxContentWidth(0.0f),
265 	fInitMatrixSize(NULL),
266 	fExtraMenuData(NULL),
267 	fTrigger(0),
268 	fResizeToFit(true),
269 	fUseCachedMenuLayout(false),
270 	fEnabled(true),
271 	fDynamicName(false),
272 	fRadioMode(false),
273 	fTrackNewBounds(false),
274 	fStickyMode(false),
275 	fIgnoreHidden(true),
276 	fTriggerEnabled(true),
277 	fRedrawAfterSticky(false),
278 	fAttachAborted(false)
279 {
280 	_InitData(NULL);
281 }
282 
283 
284 BMenu::BMenu(BMessage* archive)
285 	:
286 	BView(archive),
287 	fChosenItem(NULL),
288 	fPad(14.0f, 2.0f, 20.0f, 0.0f),
289 	fSelected(NULL),
290 	fCachedMenuWindow(NULL),
291 	fSuper(NULL),
292 	fSuperitem(NULL),
293 	fAscent(-1.0f),
294 	fDescent(-1.0f),
295 	fFontHeight(-1.0f),
296 	fState(MENU_STATE_CLOSED),
297 	fLayout(B_ITEMS_IN_ROW),
298 	fExtraRect(NULL),
299 	fMaxContentWidth(0.0f),
300 	fInitMatrixSize(NULL),
301 	fExtraMenuData(NULL),
302 	fTrigger(0),
303 	fResizeToFit(true),
304 	fUseCachedMenuLayout(false),
305 	fEnabled(true),
306 	fDynamicName(false),
307 	fRadioMode(false),
308 	fTrackNewBounds(false),
309 	fStickyMode(false),
310 	fIgnoreHidden(true),
311 	fTriggerEnabled(true),
312 	fRedrawAfterSticky(false),
313 	fAttachAborted(false)
314 {
315 	_InitData(archive);
316 }
317 
318 
319 BMenu::~BMenu()
320 {
321 	_DeleteMenuWindow();
322 
323 	RemoveItems(0, CountItems(), true);
324 
325 	delete fInitMatrixSize;
326 	delete fExtraMenuData;
327 	delete fLayoutData;
328 }
329 
330 
331 BArchivable*
332 BMenu::Instantiate(BMessage* archive)
333 {
334 	if (validate_instantiation(archive, "BMenu"))
335 		return new (nothrow) BMenu(archive);
336 
337 	return NULL;
338 }
339 
340 
341 status_t
342 BMenu::Archive(BMessage* data, bool deep) const
343 {
344 	status_t err = BView::Archive(data, deep);
345 
346 	if (err == B_OK && Layout() != B_ITEMS_IN_ROW)
347 		err = data->AddInt32("_layout", Layout());
348 	if (err == B_OK)
349 		err = data->AddBool("_rsize_to_fit", fResizeToFit);
350 	if (err == B_OK)
351 		err = data->AddBool("_disable", !IsEnabled());
352 	if (err ==  B_OK)
353 		err = data->AddBool("_radio", IsRadioMode());
354 	if (err == B_OK)
355 		err = data->AddBool("_trig_disabled", AreTriggersEnabled());
356 	if (err == B_OK)
357 		err = data->AddBool("_dyn_label", fDynamicName);
358 	if (err == B_OK)
359 		err = data->AddFloat("_maxwidth", fMaxContentWidth);
360 	if (err == B_OK && deep) {
361 		BMenuItem* item = NULL;
362 		int32 index = 0;
363 		while ((item = ItemAt(index++)) != NULL) {
364 			BMessage itemData;
365 			item->Archive(&itemData, deep);
366 			err = data->AddMessage("_items", &itemData);
367 			if (err != B_OK)
368 				break;
369 			if (fLayout == B_ITEMS_IN_MATRIX) {
370 				err = data->AddRect("_i_frames", item->fBounds);
371 			}
372 		}
373 	}
374 
375 	return err;
376 }
377 
378 
379 void
380 BMenu::AttachedToWindow()
381 {
382 	BView::AttachedToWindow();
383 
384 	_GetShiftKey(sShiftKey);
385 	_GetControlKey(sControlKey);
386 	_GetCommandKey(sCommandKey);
387 	_GetOptionKey(sOptionKey);
388 	_GetMenuKey(sMenuKey);
389 
390 	fAttachAborted = _AddDynamicItems();
391 
392 	if (!fAttachAborted) {
393 		_CacheFontInfo();
394 		_LayoutItems(0);
395 		_UpdateWindowViewSize(false);
396 	}
397 }
398 
399 
400 void
401 BMenu::DetachedFromWindow()
402 {
403 	BView::DetachedFromWindow();
404 }
405 
406 
407 void
408 BMenu::AllAttached()
409 {
410 	BView::AllAttached();
411 }
412 
413 
414 void
415 BMenu::AllDetached()
416 {
417 	BView::AllDetached();
418 }
419 
420 
421 void
422 BMenu::Draw(BRect updateRect)
423 {
424 	if (_RelayoutIfNeeded()) {
425 		Invalidate();
426 		return;
427 	}
428 
429 	DrawBackground(updateRect);
430 	_DrawItems(updateRect);
431 }
432 
433 
434 void
435 BMenu::MessageReceived(BMessage* message)
436 {
437 	switch (message->what) {
438 		case B_MOUSE_WHEEL_CHANGED:
439 		{
440 			float deltaY = 0;
441 			message->FindFloat("be:wheel_delta_y", &deltaY);
442 			if (deltaY == 0)
443 				return;
444 
445 			BMenuWindow* window = dynamic_cast<BMenuWindow*>(Window());
446 			if (window == NULL)
447 				return;
448 
449 			float largeStep;
450 			float smallStep;
451 			window->GetSteps(&smallStep, &largeStep);
452 
453 			// pressing the shift key scrolls faster
454 			if ((modifiers() & B_SHIFT_KEY) != 0)
455 				deltaY *= largeStep;
456 			else
457 				deltaY *= smallStep;
458 
459 			window->TryScrollBy(deltaY);
460 			break;
461 		}
462 
463 		default:
464 			BView::MessageReceived(message);
465 			break;
466 	}
467 }
468 
469 
470 void
471 BMenu::KeyDown(const char* bytes, int32 numBytes)
472 {
473 	// TODO: Test how it works on BeOS R5 and implement this correctly
474 	switch (bytes[0]) {
475 		case B_UP_ARROW:
476 			if (fLayout == B_ITEMS_IN_COLUMN)
477 				_SelectNextItem(fSelected, false);
478 			break;
479 
480 		case B_DOWN_ARROW:
481 		{
482 			BMenuBar* bar = dynamic_cast<BMenuBar*>(Supermenu());
483 			if (bar != NULL && fState == MENU_STATE_CLOSED) {
484 				// tell MenuBar's _Track:
485 				bar->fState = MENU_STATE_KEY_TO_SUBMENU;
486 			}
487 			if (fLayout == B_ITEMS_IN_COLUMN)
488 				_SelectNextItem(fSelected, true);
489 			break;
490 		}
491 
492 		case B_LEFT_ARROW:
493 			if (fLayout == B_ITEMS_IN_ROW)
494 				_SelectNextItem(fSelected, false);
495 			else {
496 				// this case has to be handled a bit specially.
497 				BMenuItem* item = Superitem();
498 				if (item) {
499 					if (dynamic_cast<BMenuBar*>(Supermenu())) {
500 						// If we're at the top menu below the menu bar, pass
501 						// the keypress to the menu bar so we can move to
502 						// another top level menu.
503 						BMessenger msgr(Supermenu());
504 						msgr.SendMessage(Window()->CurrentMessage());
505 					} else {
506 						// tell _Track
507 						fState = MENU_STATE_KEY_LEAVE_SUBMENU;
508 					}
509 				}
510 			}
511 			break;
512 
513 		case B_RIGHT_ARROW:
514 			if (fLayout == B_ITEMS_IN_ROW)
515 				_SelectNextItem(fSelected, true);
516 			else {
517 				if (fSelected != NULL && fSelected->Submenu() != NULL) {
518 					fSelected->Submenu()->_SetStickyMode(true);
519 						// fix me: this shouldn't be needed but dynamic menus
520 						// aren't getting it set correctly when keyboard
521 						// navigating, which aborts the attach
522 					fState = MENU_STATE_KEY_TO_SUBMENU;
523 					_SelectItem(fSelected, true, true, true);
524 				} else if (dynamic_cast<BMenuBar*>(Supermenu())) {
525 					// if we have no submenu and we're an
526 					// item in the top menu below the menubar,
527 					// pass the keypress to the menubar
528 					// so you can use the keypress to switch menus.
529 					BMessenger msgr(Supermenu());
530 					msgr.SendMessage(Window()->CurrentMessage());
531 				}
532 			}
533 			break;
534 
535 		case B_PAGE_UP:
536 		case B_PAGE_DOWN:
537 		{
538 			BMenuWindow* window = dynamic_cast<BMenuWindow*>(Window());
539 			if (window == NULL || !window->HasScrollers())
540 				break;
541 
542 			int32 deltaY = bytes[0] == B_PAGE_UP ? -1 : 1;
543 
544 			float largeStep;
545 			window->GetSteps(NULL, &largeStep);
546 			window->TryScrollBy(deltaY * largeStep);
547 			break;
548 		}
549 
550 		case B_ENTER:
551 		case B_SPACE:
552 			if (fSelected != NULL) {
553 				fChosenItem = fSelected;
554 					// preserve for exit handling
555 				_QuitTracking(false);
556 			}
557 			break;
558 
559 		case B_ESCAPE:
560 			_SelectItem(NULL);
561 			if (fState == MENU_STATE_CLOSED
562 				&& dynamic_cast<BMenuBar*>(Supermenu())) {
563 				// Keyboard may show menu without tracking it
564 				BMessenger messenger(Supermenu());
565 				messenger.SendMessage(Window()->CurrentMessage());
566 			} else
567 				_QuitTracking(false);
568 			break;
569 
570 		default:
571 		{
572 			uint32 trigger = UTF8ToCharCode(&bytes);
573 
574 			for (uint32 i = CountItems(); i-- > 0;) {
575 				BMenuItem* item = ItemAt(i);
576 				if (item->fTriggerIndex < 0 || item->fTrigger != trigger)
577 					continue;
578 
579 				_InvokeItem(item);
580 				_QuitTracking(false);
581 				break;
582 			}
583 			break;
584 		}
585 	}
586 }
587 
588 
589 BSize
590 BMenu::MinSize()
591 {
592 	_ValidatePreferredSize();
593 
594 	BSize size = (GetLayout() != NULL ? GetLayout()->MinSize()
595 		: fLayoutData->preferred);
596 
597 	return BLayoutUtils::ComposeSize(ExplicitMinSize(), size);
598 }
599 
600 
601 BSize
602 BMenu::MaxSize()
603 {
604 	_ValidatePreferredSize();
605 
606 	BSize size = (GetLayout() != NULL ? GetLayout()->MaxSize()
607 		: fLayoutData->preferred);
608 
609 	return BLayoutUtils::ComposeSize(ExplicitMaxSize(), size);
610 }
611 
612 
613 BSize
614 BMenu::PreferredSize()
615 {
616 	_ValidatePreferredSize();
617 
618 	BSize size = (GetLayout() != NULL ? GetLayout()->PreferredSize()
619 		: fLayoutData->preferred);
620 
621 	return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), size);
622 }
623 
624 
625 void
626 BMenu::GetPreferredSize(float* _width, float* _height)
627 {
628 	_ValidatePreferredSize();
629 
630 	if (_width)
631 		*_width = fLayoutData->preferred.width;
632 
633 	if (_height)
634 		*_height = fLayoutData->preferred.height;
635 }
636 
637 
638 void
639 BMenu::ResizeToPreferred()
640 {
641 	BView::ResizeToPreferred();
642 }
643 
644 
645 void
646 BMenu::DoLayout()
647 {
648 	// If the user set a layout, we let the base class version call its
649 	// hook.
650 	if (GetLayout() != NULL) {
651 		BView::DoLayout();
652 		return;
653 	}
654 
655 	if (_RelayoutIfNeeded())
656 		Invalidate();
657 }
658 
659 
660 void
661 BMenu::FrameMoved(BPoint new_position)
662 {
663 	BView::FrameMoved(new_position);
664 }
665 
666 
667 void
668 BMenu::FrameResized(float new_width, float new_height)
669 {
670 	BView::FrameResized(new_width, new_height);
671 }
672 
673 
674 void
675 BMenu::InvalidateLayout()
676 {
677 	fUseCachedMenuLayout = false;
678 	// This method exits for backwards compatibility reasons, it is used to
679 	// invalidate the menu layout, but we also use call
680 	// BView::InvalidateLayout() for good measure. Don't delete this method!
681 	BView::InvalidateLayout(false);
682 }
683 
684 
685 void
686 BMenu::MakeFocus(bool focused)
687 {
688 	BView::MakeFocus(focused);
689 }
690 
691 
692 bool
693 BMenu::AddItem(BMenuItem* item)
694 {
695 	return AddItem(item, CountItems());
696 }
697 
698 
699 bool
700 BMenu::AddItem(BMenuItem* item, int32 index)
701 {
702 	if (fLayout == B_ITEMS_IN_MATRIX) {
703 		debugger("BMenu::AddItem(BMenuItem*, int32) this method can only "
704 			"be called if the menu layout is not B_ITEMS_IN_MATRIX");
705 	}
706 
707 	if (!item || !_AddItem(item, index))
708 		return false;
709 
710 	InvalidateLayout();
711 	if (LockLooper()) {
712 		if (!Window()->IsHidden()) {
713 			_LayoutItems(index);
714 			_UpdateWindowViewSize(false);
715 			Invalidate();
716 		}
717 		UnlockLooper();
718 	}
719 
720 	return true;
721 }
722 
723 
724 bool
725 BMenu::AddItem(BMenuItem* item, BRect frame)
726 {
727 	if (fLayout != B_ITEMS_IN_MATRIX) {
728 		debugger("BMenu::AddItem(BMenuItem*, BRect) this method can only "
729 			"be called if the menu layout is B_ITEMS_IN_MATRIX");
730 	}
731 
732 	if (item == NULL)
733 		return false;
734 
735 	item->fBounds = frame;
736 
737 	int32 index = CountItems();
738 	if (!_AddItem(item, index))
739 		return false;
740 
741 	if (LockLooper()) {
742 		if (!Window()->IsHidden()) {
743 			_LayoutItems(index);
744 			Invalidate();
745 		}
746 		UnlockLooper();
747 	}
748 
749 	return true;
750 }
751 
752 
753 bool
754 BMenu::AddItem(BMenu* submenu)
755 {
756 	BMenuItem* item = new (nothrow) BMenuItem(submenu);
757 	if (item == NULL)
758 		return false;
759 
760 	if (!AddItem(item, CountItems())) {
761 		item->fSubmenu = NULL;
762 		delete item;
763 		return false;
764 	}
765 
766 	return true;
767 }
768 
769 
770 bool
771 BMenu::AddItem(BMenu* submenu, int32 index)
772 {
773 	if (fLayout == B_ITEMS_IN_MATRIX) {
774 		debugger("BMenu::AddItem(BMenuItem*, int32) this method can only "
775 			"be called if the menu layout is not B_ITEMS_IN_MATRIX");
776 	}
777 
778 	BMenuItem* item = new (nothrow) BMenuItem(submenu);
779 	if (item == NULL)
780 		return false;
781 
782 	if (!AddItem(item, index)) {
783 		item->fSubmenu = NULL;
784 		delete item;
785 		return false;
786 	}
787 
788 	return true;
789 }
790 
791 
792 bool
793 BMenu::AddItem(BMenu* submenu, BRect frame)
794 {
795 	if (fLayout != B_ITEMS_IN_MATRIX) {
796 		debugger("BMenu::AddItem(BMenu*, BRect) this method can only "
797 			"be called if the menu layout is B_ITEMS_IN_MATRIX");
798 	}
799 
800 	BMenuItem* item = new (nothrow) BMenuItem(submenu);
801 	if (item == NULL)
802 		return false;
803 
804 	if (!AddItem(item, frame)) {
805 		item->fSubmenu = NULL;
806 		delete item;
807 		return false;
808 	}
809 
810 	return true;
811 }
812 
813 
814 bool
815 BMenu::AddList(BList* list, int32 index)
816 {
817 	// TODO: test this function, it's not documented in the bebook.
818 	if (list == NULL)
819 		return false;
820 
821 	bool locked = LockLooper();
822 
823 	int32 numItems = list->CountItems();
824 	for (int32 i = 0; i < numItems; i++) {
825 		BMenuItem* item = static_cast<BMenuItem*>(list->ItemAt(i));
826 		if (item != NULL) {
827 			if (!_AddItem(item, index + i))
828 				break;
829 		}
830 	}
831 
832 	InvalidateLayout();
833 	if (locked && Window() != NULL && !Window()->IsHidden()) {
834 		// Make sure we update the layout if needed.
835 		_LayoutItems(index);
836 		_UpdateWindowViewSize(false);
837 		Invalidate();
838 	}
839 
840 	if (locked)
841 		UnlockLooper();
842 
843 	return true;
844 }
845 
846 
847 bool
848 BMenu::AddSeparatorItem()
849 {
850 	BMenuItem* item = new (nothrow) BSeparatorItem();
851 	if (!item || !AddItem(item, CountItems())) {
852 		delete item;
853 		return false;
854 	}
855 
856 	return true;
857 }
858 
859 
860 bool
861 BMenu::RemoveItem(BMenuItem* item)
862 {
863 	return _RemoveItems(0, 0, item, false);
864 }
865 
866 
867 BMenuItem*
868 BMenu::RemoveItem(int32 index)
869 {
870 	BMenuItem* item = ItemAt(index);
871 	if (item != NULL)
872 		_RemoveItems(0, 0, item, false);
873 	return item;
874 }
875 
876 
877 bool
878 BMenu::RemoveItems(int32 index, int32 count, bool deleteItems)
879 {
880 	return _RemoveItems(index, count, NULL, deleteItems);
881 }
882 
883 
884 bool
885 BMenu::RemoveItem(BMenu* submenu)
886 {
887 	for (int32 i = 0; i < fItems.CountItems(); i++) {
888 		if (static_cast<BMenuItem*>(fItems.ItemAtFast(i))->Submenu()
889 				== submenu) {
890 			return _RemoveItems(i, 1, NULL, false);
891 		}
892 	}
893 
894 	return false;
895 }
896 
897 
898 int32
899 BMenu::CountItems() const
900 {
901 	return fItems.CountItems();
902 }
903 
904 
905 BMenuItem*
906 BMenu::ItemAt(int32 index) const
907 {
908 	return static_cast<BMenuItem*>(fItems.ItemAt(index));
909 }
910 
911 
912 BMenu*
913 BMenu::SubmenuAt(int32 index) const
914 {
915 	BMenuItem* item = static_cast<BMenuItem*>(fItems.ItemAt(index));
916 	return item != NULL ? item->Submenu() : NULL;
917 }
918 
919 
920 int32
921 BMenu::IndexOf(BMenuItem* item) const
922 {
923 	return fItems.IndexOf(item);
924 }
925 
926 
927 int32
928 BMenu::IndexOf(BMenu* submenu) const
929 {
930 	for (int32 i = 0; i < fItems.CountItems(); i++) {
931 		if (ItemAt(i)->Submenu() == submenu)
932 			return i;
933 	}
934 
935 	return -1;
936 }
937 
938 
939 BMenuItem*
940 BMenu::FindItem(const char* label) const
941 {
942 	BMenuItem* item = NULL;
943 
944 	for (int32 i = 0; i < CountItems(); i++) {
945 		item = ItemAt(i);
946 
947 		if (item->Label() && strcmp(item->Label(), label) == 0)
948 			return item;
949 
950 		if (item->Submenu() != NULL) {
951 			item = item->Submenu()->FindItem(label);
952 			if (item != NULL)
953 				return item;
954 		}
955 	}
956 
957 	return NULL;
958 }
959 
960 
961 BMenuItem*
962 BMenu::FindItem(uint32 command) const
963 {
964 	BMenuItem* item = NULL;
965 
966 	for (int32 i = 0; i < CountItems(); i++) {
967 		item = ItemAt(i);
968 
969 		if (item->Command() == command)
970 			return item;
971 
972 		if (item->Submenu() != NULL) {
973 			item = item->Submenu()->FindItem(command);
974 			if (item != NULL)
975 				return item;
976 		}
977 	}
978 
979 	return NULL;
980 }
981 
982 
983 status_t
984 BMenu::SetTargetForItems(BHandler* handler)
985 {
986 	status_t status = B_OK;
987 	for (int32 i = 0; i < fItems.CountItems(); i++) {
988 		status = ItemAt(i)->SetTarget(handler);
989 		if (status < B_OK)
990 			break;
991 	}
992 
993 	return status;
994 }
995 
996 
997 status_t
998 BMenu::SetTargetForItems(BMessenger messenger)
999 {
1000 	status_t status = B_OK;
1001 	for (int32 i = 0; i < fItems.CountItems(); i++) {
1002 		status = ItemAt(i)->SetTarget(messenger);
1003 		if (status < B_OK)
1004 			break;
1005 	}
1006 
1007 	return status;
1008 }
1009 
1010 
1011 void
1012 BMenu::SetEnabled(bool enable)
1013 {
1014 	if (fEnabled == enable)
1015 		return;
1016 
1017 	fEnabled = enable;
1018 
1019 	if (dynamic_cast<_BMCMenuBar_*>(Supermenu()) != NULL)
1020 		Supermenu()->SetEnabled(enable);
1021 
1022 	if (fSuperitem)
1023 		fSuperitem->SetEnabled(enable);
1024 }
1025 
1026 
1027 void
1028 BMenu::SetRadioMode(bool on)
1029 {
1030 	fRadioMode = on;
1031 	if (!on)
1032 		SetLabelFromMarked(false);
1033 }
1034 
1035 
1036 void
1037 BMenu::SetTriggersEnabled(bool enable)
1038 {
1039 	fTriggerEnabled = enable;
1040 }
1041 
1042 
1043 void
1044 BMenu::SetMaxContentWidth(float width)
1045 {
1046 	fMaxContentWidth = width;
1047 }
1048 
1049 
1050 void
1051 BMenu::SetLabelFromMarked(bool on)
1052 {
1053 	fDynamicName = on;
1054 	if (on)
1055 		SetRadioMode(true);
1056 }
1057 
1058 
1059 bool
1060 BMenu::IsLabelFromMarked()
1061 {
1062 	return fDynamicName;
1063 }
1064 
1065 
1066 bool
1067 BMenu::IsEnabled() const
1068 {
1069 	if (!fEnabled)
1070 		return false;
1071 
1072 	return fSuper ? fSuper->IsEnabled() : true ;
1073 }
1074 
1075 
1076 bool
1077 BMenu::IsRadioMode() const
1078 {
1079 	return fRadioMode;
1080 }
1081 
1082 
1083 bool
1084 BMenu::AreTriggersEnabled() const
1085 {
1086 	return fTriggerEnabled;
1087 }
1088 
1089 
1090 bool
1091 BMenu::IsRedrawAfterSticky() const
1092 {
1093 	return fRedrawAfterSticky;
1094 }
1095 
1096 
1097 float
1098 BMenu::MaxContentWidth() const
1099 {
1100 	return fMaxContentWidth;
1101 }
1102 
1103 
1104 BMenuItem*
1105 BMenu::FindMarked()
1106 {
1107 	for (int32 i = 0; i < fItems.CountItems(); i++) {
1108 		BMenuItem* item = ItemAt(i);
1109 
1110 		if (item->IsMarked())
1111 			return item;
1112 	}
1113 
1114 	return NULL;
1115 }
1116 
1117 
1118 int32
1119 BMenu::FindMarkedIndex()
1120 {
1121 	for (int32 i = 0; i < fItems.CountItems(); i++) {
1122 		BMenuItem* item = ItemAt(i);
1123 
1124 		if (item->IsMarked())
1125 			return i;
1126 	}
1127 
1128 	return -1;
1129 }
1130 
1131 
1132 BMenu*
1133 BMenu::Supermenu() const
1134 {
1135 	return fSuper;
1136 }
1137 
1138 
1139 BMenuItem*
1140 BMenu::Superitem() const
1141 {
1142 	return fSuperitem;
1143 }
1144 
1145 
1146 BHandler*
1147 BMenu::ResolveSpecifier(BMessage* msg, int32 index, BMessage* specifier,
1148 	int32 form, const char* property)
1149 {
1150 	BPropertyInfo propInfo(sPropList);
1151 	BHandler* target = NULL;
1152 
1153 	switch (propInfo.FindMatch(msg, 0, specifier, form, property)) {
1154 		case B_ERROR:
1155 			break;
1156 
1157 		case 0:
1158 		case 1:
1159 		case 2:
1160 		case 3:
1161 		case 4:
1162 		case 5:
1163 		case 6:
1164 		case 7:
1165 			target = this;
1166 			break;
1167 		case 8:
1168 			// TODO: redirect to menu
1169 			target = this;
1170 			break;
1171 		case 9:
1172 		case 10:
1173 		case 11:
1174 		case 12:
1175 			target = this;
1176 			break;
1177 		case 13:
1178 			// TODO: redirect to menuitem
1179 			target = this;
1180 			break;
1181 	}
1182 
1183 	if (!target)
1184 		target = BView::ResolveSpecifier(msg, index, specifier, form,
1185 		property);
1186 
1187 	return target;
1188 }
1189 
1190 
1191 status_t
1192 BMenu::GetSupportedSuites(BMessage* data)
1193 {
1194 	if (data == NULL)
1195 		return B_BAD_VALUE;
1196 
1197 	status_t err = data->AddString("suites", "suite/vnd.Be-menu");
1198 
1199 	if (err < B_OK)
1200 		return err;
1201 
1202 	BPropertyInfo propertyInfo(sPropList);
1203 	err = data->AddFlat("messages", &propertyInfo);
1204 
1205 	if (err < B_OK)
1206 		return err;
1207 
1208 	return BView::GetSupportedSuites(data);
1209 }
1210 
1211 
1212 status_t
1213 BMenu::Perform(perform_code code, void* _data)
1214 {
1215 	switch (code) {
1216 		case PERFORM_CODE_MIN_SIZE:
1217 			((perform_data_min_size*)_data)->return_value
1218 				= BMenu::MinSize();
1219 			return B_OK;
1220 
1221 		case PERFORM_CODE_MAX_SIZE:
1222 			((perform_data_max_size*)_data)->return_value
1223 				= BMenu::MaxSize();
1224 			return B_OK;
1225 
1226 		case PERFORM_CODE_PREFERRED_SIZE:
1227 			((perform_data_preferred_size*)_data)->return_value
1228 				= BMenu::PreferredSize();
1229 			return B_OK;
1230 
1231 		case PERFORM_CODE_LAYOUT_ALIGNMENT:
1232 			((perform_data_layout_alignment*)_data)->return_value
1233 				= BMenu::LayoutAlignment();
1234 			return B_OK;
1235 
1236 		case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH:
1237 			((perform_data_has_height_for_width*)_data)->return_value
1238 				= BMenu::HasHeightForWidth();
1239 			return B_OK;
1240 
1241 		case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH:
1242 		{
1243 			perform_data_get_height_for_width* data
1244 				= (perform_data_get_height_for_width*)_data;
1245 			BMenu::GetHeightForWidth(data->width, &data->min, &data->max,
1246 				&data->preferred);
1247 			return B_OK;
1248 		}
1249 
1250 		case PERFORM_CODE_SET_LAYOUT:
1251 		{
1252 			perform_data_set_layout* data = (perform_data_set_layout*)_data;
1253 			BMenu::SetLayout(data->layout);
1254 			return B_OK;
1255 		}
1256 
1257 		case PERFORM_CODE_LAYOUT_INVALIDATED:
1258 		{
1259 			perform_data_layout_invalidated* data
1260 				= (perform_data_layout_invalidated*)_data;
1261 			BMenu::LayoutInvalidated(data->descendants);
1262 			return B_OK;
1263 		}
1264 
1265 		case PERFORM_CODE_DO_LAYOUT:
1266 		{
1267 			BMenu::DoLayout();
1268 			return B_OK;
1269 		}
1270 	}
1271 
1272 	return BView::Perform(code, _data);
1273 }
1274 
1275 
1276 // #pragma mark - BMenu protected methods
1277 
1278 
1279 BMenu::BMenu(BRect frame, const char* name, uint32 resizingMode, uint32 flags,
1280 	menu_layout layout, bool resizeToFit)
1281 	:
1282 	BView(frame, name, resizingMode, flags),
1283 	fChosenItem(NULL),
1284 	fSelected(NULL),
1285 	fCachedMenuWindow(NULL),
1286 	fSuper(NULL),
1287 	fSuperitem(NULL),
1288 	fAscent(-1.0f),
1289 	fDescent(-1.0f),
1290 	fFontHeight(-1.0f),
1291 	fState(MENU_STATE_CLOSED),
1292 	fLayout(layout),
1293 	fExtraRect(NULL),
1294 	fMaxContentWidth(0.0f),
1295 	fInitMatrixSize(NULL),
1296 	fExtraMenuData(NULL),
1297 	fTrigger(0),
1298 	fResizeToFit(resizeToFit),
1299 	fUseCachedMenuLayout(false),
1300 	fEnabled(true),
1301 	fDynamicName(false),
1302 	fRadioMode(false),
1303 	fTrackNewBounds(false),
1304 	fStickyMode(false),
1305 	fIgnoreHidden(true),
1306 	fTriggerEnabled(true),
1307 	fRedrawAfterSticky(false),
1308 	fAttachAborted(false)
1309 {
1310 	_InitData(NULL);
1311 }
1312 
1313 
1314 void
1315 BMenu::SetItemMargins(float left, float top, float right, float bottom)
1316 {
1317 	fPad.Set(left, top, right, bottom);
1318 }
1319 
1320 
1321 void
1322 BMenu::GetItemMargins(float* _left, float* _top, float* _right,
1323 	float* _bottom) const
1324 {
1325 	if (_left != NULL)
1326 		*_left = fPad.left;
1327 
1328 	if (_top != NULL)
1329 		*_top = fPad.top;
1330 
1331 	if (_right != NULL)
1332 		*_right = fPad.right;
1333 
1334 	if (_bottom != NULL)
1335 		*_bottom = fPad.bottom;
1336 }
1337 
1338 
1339 menu_layout
1340 BMenu::Layout() const
1341 {
1342 	return fLayout;
1343 }
1344 
1345 
1346 void
1347 BMenu::Show()
1348 {
1349 	Show(false);
1350 }
1351 
1352 
1353 void
1354 BMenu::Show(bool selectFirst)
1355 {
1356 	_Install(NULL);
1357 	_Show(selectFirst);
1358 }
1359 
1360 
1361 void
1362 BMenu::Hide()
1363 {
1364 	_Hide();
1365 	_Uninstall();
1366 }
1367 
1368 
1369 BMenuItem*
1370 BMenu::Track(bool sticky, BRect* clickToOpenRect)
1371 {
1372 	if (sticky && LockLooper()) {
1373 		//RedrawAfterSticky(Bounds());
1374 			// the call above didn't do anything, so I've removed it for now
1375 		UnlockLooper();
1376 	}
1377 
1378 	if (clickToOpenRect != NULL && LockLooper()) {
1379 		fExtraRect = clickToOpenRect;
1380 		ConvertFromScreen(fExtraRect);
1381 		UnlockLooper();
1382 	}
1383 
1384 	_SetStickyMode(sticky);
1385 
1386 	int action;
1387 	BMenuItem* menuItem = _Track(&action);
1388 
1389 	fExtraRect = NULL;
1390 
1391 	return menuItem;
1392 }
1393 
1394 
1395 // #pragma mark - BMenu private methods
1396 
1397 
1398 bool
1399 BMenu::AddDynamicItem(add_state state)
1400 {
1401 	// Implemented in subclasses
1402 	return false;
1403 }
1404 
1405 
1406 void
1407 BMenu::DrawBackground(BRect updateRect)
1408 {
1409 	if (be_control_look != NULL) {
1410 		rgb_color base = ui_color(B_MENU_BACKGROUND_COLOR);
1411 		uint32 flags = 0;
1412 		if (!IsEnabled())
1413 			flags |= BControlLook::B_DISABLED;
1414 
1415 		if (IsFocus())
1416 			flags |= BControlLook::B_FOCUSED;
1417 
1418 		BRect rect = Bounds();
1419 		uint32 borders = BControlLook::B_LEFT_BORDER
1420 			| BControlLook::B_RIGHT_BORDER;
1421 		if (Window() != NULL && Parent() != NULL) {
1422 			if (Parent()->Frame().top == Window()->Bounds().top)
1423 				borders |= BControlLook::B_TOP_BORDER;
1424 
1425 			if (Parent()->Frame().bottom == Window()->Bounds().bottom)
1426 				borders |= BControlLook::B_BOTTOM_BORDER;
1427 		} else {
1428 			borders |= BControlLook::B_TOP_BORDER
1429 				| BControlLook::B_BOTTOM_BORDER;
1430 		}
1431 		be_control_look->DrawMenuBackground(this, rect, updateRect, base, 0,
1432 			borders);
1433 
1434 		return;
1435 	}
1436 
1437 	rgb_color oldColor = HighColor();
1438 	SetHighColor(ui_color(B_MENU_BACKGROUND_COLOR));
1439 	FillRect(Bounds() & updateRect, B_SOLID_HIGH);
1440 	SetHighColor(oldColor);
1441 }
1442 
1443 
1444 void
1445 BMenu::SetTrackingHook(menu_tracking_hook func, void* state)
1446 {
1447 	delete fExtraMenuData;
1448 	fExtraMenuData = new (nothrow) BPrivate::ExtraMenuData(func, state);
1449 }
1450 
1451 
1452 void BMenu::_ReservedMenu3() {}
1453 void BMenu::_ReservedMenu4() {}
1454 void BMenu::_ReservedMenu5() {}
1455 void BMenu::_ReservedMenu6() {}
1456 
1457 
1458 void
1459 BMenu::_InitData(BMessage* archive)
1460 {
1461 	BPrivate::kEmptyMenuLabel = B_TRANSLATE("<empty>");
1462 
1463 	// TODO: Get _color, _fname, _fflt from the message, if present
1464 	BFont font;
1465 	font.SetFamilyAndStyle(sMenuInfo.f_family, sMenuInfo.f_style);
1466 	font.SetSize(sMenuInfo.font_size);
1467 	SetFont(&font, B_FONT_FAMILY_AND_STYLE | B_FONT_SIZE);
1468 
1469 	fLayoutData = new LayoutData;
1470 	fLayoutData->lastResizingMode = ResizingMode();
1471 
1472 	SetLowUIColor(B_MENU_BACKGROUND_COLOR);
1473 	SetViewColor(B_TRANSPARENT_COLOR);
1474 
1475 	fTriggerEnabled = sMenuInfo.triggers_always_shown;
1476 
1477 	if (archive != NULL) {
1478 		archive->FindInt32("_layout", (int32*)&fLayout);
1479 		archive->FindBool("_rsize_to_fit", &fResizeToFit);
1480 		bool disabled;
1481 		if (archive->FindBool("_disable", &disabled) == B_OK)
1482 			fEnabled = !disabled;
1483 		archive->FindBool("_radio", &fRadioMode);
1484 
1485 		bool disableTrigger = false;
1486 		archive->FindBool("_trig_disabled", &disableTrigger);
1487 		fTriggerEnabled = !disableTrigger;
1488 
1489 		archive->FindBool("_dyn_label", &fDynamicName);
1490 		archive->FindFloat("_maxwidth", &fMaxContentWidth);
1491 
1492 		BMessage msg;
1493 		for (int32 i = 0; archive->FindMessage("_items", i, &msg) == B_OK; i++) {
1494 			BArchivable* object = instantiate_object(&msg);
1495 			if (BMenuItem* item = dynamic_cast<BMenuItem*>(object)) {
1496 				BRect bounds;
1497 				if (fLayout == B_ITEMS_IN_MATRIX
1498 					&& archive->FindRect("_i_frames", i, &bounds) == B_OK)
1499 					AddItem(item, bounds);
1500 				else
1501 					AddItem(item);
1502 			}
1503 		}
1504 	}
1505 }
1506 
1507 
1508 bool
1509 BMenu::_Show(bool selectFirstItem, bool keyDown)
1510 {
1511 	if (Window() != NULL)
1512 		return false;
1513 
1514 	// See if the supermenu has a cached menuwindow,
1515 	// and use that one if possible.
1516 	BMenuWindow* window = NULL;
1517 	bool ourWindow = false;
1518 	if (fSuper != NULL) {
1519 		fSuperbounds = fSuper->ConvertToScreen(fSuper->Bounds());
1520 		window = fSuper->_MenuWindow();
1521 	}
1522 
1523 	// Otherwise, create a new one
1524 	// This happens for "stand alone" BPopUpMenus
1525 	// (i.e. not within a BMenuField)
1526 	if (window == NULL) {
1527 		// Menu windows get the BMenu's handler name
1528 		window = new (nothrow) BMenuWindow(Name());
1529 		ourWindow = true;
1530 	}
1531 
1532 	if (window == NULL)
1533 		return false;
1534 
1535 	if (window->Lock()) {
1536 		bool addAborted = false;
1537 		if (keyDown)
1538 			addAborted = _AddDynamicItems(keyDown);
1539 
1540 		if (addAborted) {
1541 			if (ourWindow)
1542 				window->Quit();
1543 			else
1544 				window->Unlock();
1545 			return false;
1546 		}
1547 		fAttachAborted = false;
1548 
1549 		window->AttachMenu(this);
1550 
1551 		if (ItemAt(0) != NULL) {
1552 			float width, height;
1553 			ItemAt(0)->GetContentSize(&width, &height);
1554 
1555 			window->SetSmallStep(ceilf(height));
1556 		}
1557 
1558 		// Menu didn't have the time to add its items: aborting...
1559 		if (fAttachAborted) {
1560 			window->DetachMenu();
1561 			// TODO: Probably not needed, we can just let _hide() quit the
1562 			// window.
1563 			if (ourWindow)
1564 				window->Quit();
1565 			else
1566 				window->Unlock();
1567 			return false;
1568 		}
1569 
1570 		_UpdateWindowViewSize(true);
1571 		window->Show();
1572 
1573 		if (selectFirstItem)
1574 			_SelectItem(ItemAt(0), false);
1575 
1576 		window->Unlock();
1577 	}
1578 
1579 	return true;
1580 }
1581 
1582 
1583 void
1584 BMenu::_Hide()
1585 {
1586 	BMenuWindow* window = dynamic_cast<BMenuWindow*>(Window());
1587 	if (window == NULL || !window->Lock())
1588 		return;
1589 
1590 	if (fSelected != NULL)
1591 		_SelectItem(NULL);
1592 
1593 	window->Hide();
1594 	window->DetachMenu();
1595 		// we don't want to be deleted when the window is removed
1596 
1597 #if USE_CACHED_MENUWINDOW
1598 	if (fSuper != NULL)
1599 		window->Unlock();
1600 	else
1601 #endif
1602 		window->Quit();
1603 			// it's our window, quit it
1604 
1605 	_DeleteMenuWindow();
1606 		// Delete the menu window used by our submenus
1607 }
1608 
1609 
1610 // #pragma mark - mouse tracking
1611 
1612 
1613 const static bigtime_t kOpenSubmenuDelay = 225000;
1614 const static bigtime_t kNavigationAreaTimeout = 1000000;
1615 
1616 
1617 BMenuItem*
1618 BMenu::_Track(int* action, long start)
1619 {
1620 	// TODO: cleanup
1621 	BMenuItem* item = NULL;
1622 	BRect navAreaRectAbove;
1623 	BRect navAreaRectBelow;
1624 	bigtime_t selectedTime = system_time();
1625 	bigtime_t navigationAreaTime = 0;
1626 
1627 	fState = MENU_STATE_TRACKING;
1628 	fChosenItem = NULL;
1629 		// we will use this for keyboard selection
1630 
1631 	BPoint location;
1632 	uint32 buttons = 0;
1633 	if (LockLooper()) {
1634 		GetMouse(&location, &buttons);
1635 		UnlockLooper();
1636 	}
1637 
1638 	bool releasedOnce = buttons == 0;
1639 	while (fState != MENU_STATE_CLOSED) {
1640 		if (_CustomTrackingWantsToQuit())
1641 			break;
1642 
1643 		if (!LockLooper())
1644 			break;
1645 
1646 		BMenuWindow* window = static_cast<BMenuWindow*>(Window());
1647 		BPoint screenLocation = ConvertToScreen(location);
1648 		if (window->CheckForScrolling(screenLocation)) {
1649 			UnlockLooper();
1650 			continue;
1651 		}
1652 
1653 		// The order of the checks is important
1654 		// to be able to handle overlapping menus:
1655 		// first we check if mouse is inside a submenu,
1656 		// then if the mouse is inside this menu,
1657 		// then if it's over a super menu.
1658 		if (_OverSubmenu(fSelected, screenLocation)
1659 			|| fState == MENU_STATE_KEY_TO_SUBMENU) {
1660 			if (fState == MENU_STATE_TRACKING) {
1661 				// not if from R.Arrow
1662 				fState = MENU_STATE_TRACKING_SUBMENU;
1663 			}
1664 			navAreaRectAbove = BRect();
1665 			navAreaRectBelow = BRect();
1666 
1667 			// Since the submenu has its own looper,
1668 			// we can unlock ours. Doing so also make sure
1669 			// that our window gets any update message to
1670 			// redraw itself
1671 			UnlockLooper();
1672 			int submenuAction = MENU_STATE_TRACKING;
1673 			BMenu* submenu = fSelected->Submenu();
1674 			submenu->_SetStickyMode(_IsStickyMode());
1675 
1676 			// The following call blocks until the submenu
1677 			// gives control back to us, either because the mouse
1678 			// pointer goes out of the submenu's bounds, or because
1679 			// the user closes the menu
1680 			BMenuItem* submenuItem = submenu->_Track(&submenuAction);
1681 			if (submenuAction == MENU_STATE_CLOSED) {
1682 				item = submenuItem;
1683 				fState = MENU_STATE_CLOSED;
1684 			} else if (submenuAction == MENU_STATE_KEY_LEAVE_SUBMENU) {
1685 				if (LockLooper()) {
1686 					BMenuItem* temp = fSelected;
1687 					// close the submenu:
1688 					_SelectItem(NULL);
1689 					// but reselect the item itself for user:
1690 					_SelectItem(temp, false);
1691 					UnlockLooper();
1692 				}
1693 				// cancel  key-nav state
1694 				fState = MENU_STATE_TRACKING;
1695 			} else
1696 				fState = MENU_STATE_TRACKING;
1697 			if (!LockLooper())
1698 				break;
1699 		} else if ((item = _HitTestItems(location, B_ORIGIN)) != NULL) {
1700 			_UpdateStateOpenSelect(item, location, navAreaRectAbove,
1701 				navAreaRectBelow, selectedTime, navigationAreaTime);
1702 			releasedOnce = true;
1703 		} else if (_OverSuper(screenLocation)
1704 			&& fSuper->fState != MENU_STATE_KEY_TO_SUBMENU) {
1705 			fState = MENU_STATE_TRACKING;
1706 			UnlockLooper();
1707 			break;
1708 		} else if (fState == MENU_STATE_KEY_LEAVE_SUBMENU) {
1709 			UnlockLooper();
1710 			break;
1711 		} else if (fSuper == NULL
1712 			|| fSuper->fState != MENU_STATE_KEY_TO_SUBMENU) {
1713 			// Mouse pointer outside menu:
1714 			// If there's no other submenu opened,
1715 			// deselect the current selected item
1716 			if (fSelected != NULL
1717 				&& (fSelected->Submenu() == NULL
1718 					|| fSelected->Submenu()->Window() == NULL)) {
1719 				_SelectItem(NULL);
1720 				fState = MENU_STATE_TRACKING;
1721 			}
1722 
1723 			if (fSuper != NULL) {
1724 				// Give supermenu the chance to continue tracking
1725 				*action = fState;
1726 				UnlockLooper();
1727 				return NULL;
1728 			}
1729 		}
1730 
1731 		UnlockLooper();
1732 
1733 		if (releasedOnce)
1734 			_UpdateStateClose(item, location, buttons);
1735 
1736 		if (fState != MENU_STATE_CLOSED) {
1737 			bigtime_t snoozeAmount = 50000;
1738 
1739 			BPoint newLocation = location;
1740 			uint32 newButtons = buttons;
1741 
1742 			// If user doesn't move the mouse, loop here,
1743 			// so we don't interfere with keyboard menu navigation
1744 			do {
1745 				snooze(snoozeAmount);
1746 				if (!LockLooper())
1747 					break;
1748 				GetMouse(&newLocation, &newButtons, true);
1749 				UnlockLooper();
1750 			} while (newLocation == location && newButtons == buttons
1751 				&& !(item != NULL && item->Submenu() != NULL
1752 					&& item->Submenu()->Window() == NULL)
1753 				&& fState == MENU_STATE_TRACKING);
1754 
1755 			if (newLocation != location || newButtons != buttons) {
1756 				if (!releasedOnce && newButtons == 0 && buttons != 0)
1757 					releasedOnce = true;
1758 				location = newLocation;
1759 				buttons = newButtons;
1760 			}
1761 
1762 			if (releasedOnce)
1763 				_UpdateStateClose(item, location, buttons);
1764 		}
1765 	}
1766 
1767 	if (action != NULL)
1768 		*action = fState;
1769 
1770 	// keyboard Enter will set this
1771 	if (fChosenItem != NULL)
1772 		item = fChosenItem;
1773 	else if (fSelected == NULL) {
1774 		// needed to cover (rare) mouse/ESC combination
1775 		item = NULL;
1776 	}
1777 
1778 	if (fSelected != NULL && LockLooper()) {
1779 		_SelectItem(NULL);
1780 		UnlockLooper();
1781 	}
1782 
1783 	// delete the menu window recycled for all the child menus
1784 	_DeleteMenuWindow();
1785 
1786 	return item;
1787 }
1788 
1789 
1790 void
1791 BMenu::_UpdateNavigationArea(BPoint position, BRect& navAreaRectAbove,
1792 	BRect& navAreaRectBelow)
1793 {
1794 #define NAV_AREA_THRESHOLD    8
1795 
1796 	// The navigation area is a region in which mouse-overs won't select
1797 	// the item under the cursor. This makes it easier to navigate to
1798 	// submenus, as the cursor can be moved to submenu items directly instead
1799 	// of having to move it horizontally into the submenu first. The concept
1800 	// is illustrated below:
1801 	//
1802 	// +-------+----+---------+
1803 	// |       |   /|         |
1804 	// |       |  /*|         |
1805 	// |[2]--> | /**|         |
1806 	// |       |/[4]|         |
1807 	// |------------|         |
1808 	// |    [1]     |   [6]   |
1809 	// |------------|         |
1810 	// |       |\[5]|         |
1811 	// |[3]--> | \**|         |
1812 	// |       |  \*|         |
1813 	// |       |   \|         |
1814 	// |       +----|---------+
1815 	// |            |
1816 	// +------------+
1817 	//
1818 	// [1] Selected item, cursor position ('position')
1819 	// [2] Upper navigation area rectangle ('navAreaRectAbove')
1820 	// [3] Lower navigation area rectangle ('navAreaRectBelow')
1821 	// [4] Upper navigation area
1822 	// [5] Lower navigation area
1823 	// [6] Submenu
1824 	//
1825 	// The rectangles are used to calculate if the cursor is in the actual
1826 	// navigation area (see _UpdateStateOpenSelect()).
1827 
1828 	if (fSelected == NULL)
1829 		return;
1830 
1831 	BMenu* submenu = fSelected->Submenu();
1832 
1833 	if (submenu != NULL) {
1834 		BRect menuBounds = ConvertToScreen(Bounds());
1835 
1836 		BRect submenuBounds;
1837 		if (fSelected->Submenu()->LockLooper()) {
1838 			submenuBounds = fSelected->Submenu()->ConvertToScreen(
1839 				fSelected->Submenu()->Bounds());
1840 			fSelected->Submenu()->UnlockLooper();
1841 		}
1842 
1843 		if (menuBounds.left < submenuBounds.left) {
1844 			navAreaRectAbove.Set(position.x + NAV_AREA_THRESHOLD,
1845 				submenuBounds.top, menuBounds.right,
1846 				position.y);
1847 			navAreaRectBelow.Set(position.x + NAV_AREA_THRESHOLD,
1848 				position.y, menuBounds.right,
1849 				submenuBounds.bottom);
1850 		} else {
1851 			navAreaRectAbove.Set(menuBounds.left,
1852 				submenuBounds.top, position.x - NAV_AREA_THRESHOLD,
1853 				position.y);
1854 			navAreaRectBelow.Set(menuBounds.left,
1855 				position.y, position.x - NAV_AREA_THRESHOLD,
1856 				submenuBounds.bottom);
1857 		}
1858 	} else {
1859 		navAreaRectAbove = BRect();
1860 		navAreaRectBelow = BRect();
1861 	}
1862 }
1863 
1864 
1865 void
1866 BMenu::_UpdateStateOpenSelect(BMenuItem* item, BPoint position,
1867 	BRect& navAreaRectAbove, BRect& navAreaRectBelow, bigtime_t& selectedTime,
1868 	bigtime_t& navigationAreaTime)
1869 {
1870 	if (fState == MENU_STATE_CLOSED)
1871 		return;
1872 
1873 	if (item != fSelected) {
1874 		if (navigationAreaTime == 0)
1875 			navigationAreaTime = system_time();
1876 
1877 		position = ConvertToScreen(position);
1878 
1879 		bool inNavAreaRectAbove = navAreaRectAbove.Contains(position);
1880 		bool inNavAreaRectBelow = navAreaRectBelow.Contains(position);
1881 
1882 		if (fSelected == NULL
1883 			|| (!inNavAreaRectAbove && !inNavAreaRectBelow)) {
1884 			_SelectItem(item, false);
1885 			navAreaRectAbove = BRect();
1886 			navAreaRectBelow = BRect();
1887 			selectedTime = system_time();
1888 			navigationAreaTime = 0;
1889 			return;
1890 		}
1891 
1892 		BRect menuBounds = ConvertToScreen(Bounds());
1893 
1894 		BRect submenuBounds;
1895 		if (fSelected->Submenu()->LockLooper()) {
1896 			fSelected->Submenu()->ConvertToScreen(
1897 				fSelected->Submenu()->Bounds());
1898 			fSelected->Submenu()->UnlockLooper();
1899 		}
1900 
1901 		float xOffset;
1902 
1903 		// navAreaRectAbove and navAreaRectBelow have the same X
1904 		// position and width, so it doesn't matter which one we use to
1905 		// calculate the X offset
1906 		if (menuBounds.left < submenuBounds.left)
1907 			xOffset = position.x - navAreaRectAbove.left;
1908 		else
1909 			xOffset = navAreaRectAbove.right - position.x;
1910 
1911 		bool inNavArea;
1912 
1913 		if (inNavAreaRectAbove) {
1914 			float yOffset = navAreaRectAbove.bottom - position.y;
1915 			float ratio = navAreaRectAbove.Width() / navAreaRectAbove.Height();
1916 
1917 			inNavArea = yOffset <= xOffset / ratio;
1918 		} else {
1919 			float yOffset = navAreaRectBelow.bottom - position.y;
1920 			float ratio = navAreaRectBelow.Width() / navAreaRectBelow.Height();
1921 
1922 			inNavArea = yOffset >= (navAreaRectBelow.Height() - xOffset
1923 				/ ratio);
1924 		}
1925 
1926 		bigtime_t systime = system_time();
1927 
1928 		if (!inNavArea || (navigationAreaTime > 0 && systime -
1929 			navigationAreaTime > kNavigationAreaTimeout)) {
1930 			// Don't delay opening of submenu if the user had
1931 			// to wait for the navigation area timeout anyway
1932 			_SelectItem(item, inNavArea);
1933 
1934 			if (inNavArea) {
1935 				_UpdateNavigationArea(position, navAreaRectAbove,
1936 					navAreaRectBelow);
1937 			} else {
1938 				navAreaRectAbove = BRect();
1939 				navAreaRectBelow = BRect();
1940 			}
1941 
1942 			selectedTime = system_time();
1943 			navigationAreaTime = 0;
1944 		}
1945 	} else if (fSelected->Submenu() != NULL &&
1946 		system_time() - selectedTime > kOpenSubmenuDelay) {
1947 		_SelectItem(fSelected, true);
1948 
1949 		if (!navAreaRectAbove.IsValid() && !navAreaRectBelow.IsValid()) {
1950 			position = ConvertToScreen(position);
1951 			_UpdateNavigationArea(position, navAreaRectAbove,
1952 				navAreaRectBelow);
1953 		}
1954 	}
1955 
1956 	if (fState != MENU_STATE_TRACKING)
1957 		fState = MENU_STATE_TRACKING;
1958 }
1959 
1960 
1961 void
1962 BMenu::_UpdateStateClose(BMenuItem* item, const BPoint& where,
1963 	const uint32& buttons)
1964 {
1965 	if (fState == MENU_STATE_CLOSED)
1966 		return;
1967 
1968 	if (buttons != 0 && _IsStickyMode()) {
1969 		if (item == NULL) {
1970 			if (item != fSelected && LockLooper()) {
1971 				_SelectItem(item, false);
1972 				UnlockLooper();
1973 			}
1974 			fState = MENU_STATE_CLOSED;
1975 		} else
1976 			_SetStickyMode(false);
1977 	} else if (buttons == 0 && !_IsStickyMode()) {
1978 		if (fExtraRect != NULL && fExtraRect->Contains(where)) {
1979 			_SetStickyMode(true);
1980 			fExtraRect = NULL;
1981 				// Setting this to NULL will prevent this code
1982 				// to be executed next time
1983 		} else {
1984 			if (item != fSelected && LockLooper()) {
1985 				_SelectItem(item, false);
1986 				UnlockLooper();
1987 			}
1988 			fState = MENU_STATE_CLOSED;
1989 		}
1990 	}
1991 }
1992 
1993 
1994 bool
1995 BMenu::_AddItem(BMenuItem* item, int32 index)
1996 {
1997 	ASSERT(item != NULL);
1998 	if (index < 0 || index > fItems.CountItems())
1999 		return false;
2000 
2001 	if (item->IsMarked())
2002 		_ItemMarked(item);
2003 
2004 	if (!fItems.AddItem(item, index))
2005 		return false;
2006 
2007 	// install the item on the supermenu's window
2008 	// or onto our window, if we are a root menu
2009 	BWindow* window = NULL;
2010 	if (Superitem() != NULL)
2011 		window = Superitem()->fWindow;
2012 	else
2013 		window = Window();
2014 	if (window != NULL)
2015 		item->Install(window);
2016 
2017 	item->SetSuper(this);
2018 	return true;
2019 }
2020 
2021 
2022 bool
2023 BMenu::_RemoveItems(int32 index, int32 count, BMenuItem* item,
2024 	bool deleteItems)
2025 {
2026 	bool success = false;
2027 	bool invalidateLayout = false;
2028 
2029 	bool locked = LockLooper();
2030 	BWindow* window = Window();
2031 
2032 	// The plan is simple: If we're given a BMenuItem directly, we use it
2033 	// and ignore index and count. Otherwise, we use them instead.
2034 	if (item != NULL) {
2035 		if (fItems.RemoveItem(item)) {
2036 			if (item == fSelected && window != NULL)
2037 				_SelectItem(NULL);
2038 			item->Uninstall();
2039 			item->SetSuper(NULL);
2040 			if (deleteItems)
2041 				delete item;
2042 			success = invalidateLayout = true;
2043 		}
2044 	} else {
2045 		// We iterate backwards because it's simpler
2046 		int32 i = std::min(index + count - 1, fItems.CountItems() - 1);
2047 		// NOTE: the range check for "index" is done after
2048 		// calculating the last index to be removed, so
2049 		// that the range is not "shifted" unintentionally
2050 		index = std::max((int32)0, index);
2051 		for (; i >= index; i--) {
2052 			item = static_cast<BMenuItem*>(fItems.ItemAt(i));
2053 			if (item != NULL) {
2054 				if (fItems.RemoveItem(item)) {
2055 					if (item == fSelected && window != NULL)
2056 						_SelectItem(NULL);
2057 					item->Uninstall();
2058 					item->SetSuper(NULL);
2059 					if (deleteItems)
2060 						delete item;
2061 					success = true;
2062 					invalidateLayout = true;
2063 				} else {
2064 					// operation not entirely successful
2065 					success = false;
2066 					break;
2067 				}
2068 			}
2069 		}
2070 	}
2071 
2072 	if (invalidateLayout) {
2073 		InvalidateLayout();
2074 		if (locked && window != NULL) {
2075 			_LayoutItems(0);
2076 			_UpdateWindowViewSize(false);
2077 			Invalidate();
2078 		}
2079 	}
2080 
2081 	if (locked)
2082 		UnlockLooper();
2083 
2084 	return success;
2085 }
2086 
2087 
2088 bool
2089 BMenu::_RelayoutIfNeeded()
2090 {
2091 	if (!fUseCachedMenuLayout) {
2092 		fUseCachedMenuLayout = true;
2093 		_CacheFontInfo();
2094 		_LayoutItems(0);
2095 		return true;
2096 	}
2097 	return false;
2098 }
2099 
2100 
2101 void
2102 BMenu::_LayoutItems(int32 index)
2103 {
2104 	_CalcTriggers();
2105 
2106 	float width;
2107 	float height;
2108 	_ComputeLayout(index, fResizeToFit, true, &width, &height);
2109 
2110 	if (fResizeToFit)
2111 		ResizeTo(width, height);
2112 }
2113 
2114 
2115 BSize
2116 BMenu::_ValidatePreferredSize()
2117 {
2118 	if (!fLayoutData->preferred.IsWidthSet() || ResizingMode()
2119 			!= fLayoutData->lastResizingMode) {
2120 		_ComputeLayout(0, true, false, NULL, NULL);
2121 		ResetLayoutInvalidation();
2122 	}
2123 
2124 	return fLayoutData->preferred;
2125 }
2126 
2127 
2128 void
2129 BMenu::_ComputeLayout(int32 index, bool bestFit, bool moveItems,
2130 	float* _width, float* _height)
2131 {
2132 	// TODO: Take "bestFit", "moveItems", "index" into account,
2133 	// Recalculate only the needed items,
2134 	// not the whole layout every time
2135 
2136 	fLayoutData->lastResizingMode = ResizingMode();
2137 
2138 	BRect frame;
2139 	switch (fLayout) {
2140 		case B_ITEMS_IN_COLUMN:
2141 		{
2142 			BRect parentFrame;
2143 			BRect* overrideFrame = NULL;
2144 			if (dynamic_cast<_BMCMenuBar_*>(Supermenu()) != NULL) {
2145 				// When the menu is modified while it's open, we get here in a
2146 				// situation where trying to lock the looper would deadlock
2147 				// (the window is locked waiting for the menu to terminate).
2148 				// In that case, just give up on getting the supermenu bounds
2149 				// and keep the menu at the current width and position.
2150 				if (Supermenu()->LockLooperWithTimeout(0) == B_OK) {
2151 					parentFrame = Supermenu()->Bounds();
2152 					Supermenu()->UnlockLooper();
2153 					overrideFrame = &parentFrame;
2154 				}
2155 			}
2156 
2157 			_ComputeColumnLayout(index, bestFit, moveItems, overrideFrame,
2158 				frame);
2159 			break;
2160 		}
2161 
2162 		case B_ITEMS_IN_ROW:
2163 			_ComputeRowLayout(index, bestFit, moveItems, frame);
2164 			break;
2165 
2166 		case B_ITEMS_IN_MATRIX:
2167 			_ComputeMatrixLayout(frame);
2168 			break;
2169 	}
2170 
2171 	// change width depending on resize mode
2172 	BSize size;
2173 	if ((ResizingMode() & B_FOLLOW_LEFT_RIGHT) == B_FOLLOW_LEFT_RIGHT) {
2174 		if (dynamic_cast<_BMCMenuBar_*>(this) != NULL)
2175 			size.width = Bounds().Width() - fPad.right;
2176 		else if (Parent() != NULL)
2177 			size.width = Parent()->Frame().Width() + 1;
2178 		else if (Window() != NULL)
2179 			size.width = Window()->Frame().Width() + 1;
2180 		else
2181 			size.width = Bounds().Width();
2182 	} else
2183 		size.width = frame.Width();
2184 
2185 	size.height = frame.Height();
2186 
2187 	if (_width)
2188 		*_width = size.width;
2189 
2190 	if (_height)
2191 		*_height = size.height;
2192 
2193 	if (bestFit)
2194 		fLayoutData->preferred = size;
2195 
2196 	if (moveItems)
2197 		fUseCachedMenuLayout = true;
2198 }
2199 
2200 
2201 void
2202 BMenu::_ComputeColumnLayout(int32 index, bool bestFit, bool moveItems,
2203 	BRect* overrideFrame, BRect& frame)
2204 {
2205 	bool command = false;
2206 	bool control = false;
2207 	bool shift = false;
2208 	bool option = false;
2209 
2210 	if (index > 0)
2211 		frame = ItemAt(index - 1)->Frame();
2212 	else if (overrideFrame != NULL)
2213 		frame.Set(0, 0, overrideFrame->right, -1);
2214 	else
2215 		frame.Set(0, 0, 0, -1);
2216 
2217 	BFont font;
2218 	GetFont(&font);
2219 
2220 	for (; index < fItems.CountItems(); index++) {
2221 		BMenuItem* item = ItemAt(index);
2222 
2223 		float width;
2224 		float height;
2225 		item->GetContentSize(&width, &height);
2226 
2227 		if (item->fModifiers && item->fShortcutChar) {
2228 			width += font.Size();
2229 			if ((item->fModifiers & B_COMMAND_KEY) != 0)
2230 				command = true;
2231 
2232 			if ((item->fModifiers & B_CONTROL_KEY) != 0)
2233 				control = true;
2234 
2235 			if ((item->fModifiers & B_SHIFT_KEY) != 0)
2236 				shift = true;
2237 
2238 			if ((item->fModifiers & B_OPTION_KEY) != 0)
2239 				option = true;
2240 		}
2241 
2242 		item->fBounds.left = 0.0f;
2243 		item->fBounds.top = frame.bottom + 1.0f;
2244 		item->fBounds.bottom = item->fBounds.top + height + fPad.top
2245 			+ fPad.bottom;
2246 
2247 		if (item->fSubmenu != NULL)
2248 			width += item->Frame().Height();
2249 
2250 		frame.right = std::max(frame.right, width + fPad.left + fPad.right);
2251 		frame.bottom = item->fBounds.bottom;
2252 	}
2253 
2254 	if (command) {
2255 		frame.right
2256 			+= BPrivate::MenuPrivate::MenuItemCommand()->Bounds().Width() + 1;
2257 	}
2258 	if (control) {
2259 		frame.right
2260 			+= BPrivate::MenuPrivate::MenuItemControl()->Bounds().Width() + 1;
2261 	}
2262 	if (option) {
2263 		frame.right
2264 			+= BPrivate::MenuPrivate::MenuItemOption()->Bounds().Width() + 1;
2265 	}
2266 	if (shift) {
2267 		frame.right
2268 			+= BPrivate::MenuPrivate::MenuItemShift()->Bounds().Width() + 1;
2269 	}
2270 
2271 	if (fMaxContentWidth > 0)
2272 		frame.right = std::min(frame.right, fMaxContentWidth);
2273 
2274 	if (moveItems) {
2275 		for (int32 i = 0; i < fItems.CountItems(); i++)
2276 			ItemAt(i)->fBounds.right = frame.right;
2277 	}
2278 
2279 	frame.top = 0;
2280 	frame.right = ceilf(frame.right);
2281 }
2282 
2283 
2284 void
2285 BMenu::_ComputeRowLayout(int32 index, bool bestFit, bool moveItems,
2286 	BRect& frame)
2287 {
2288 	font_height fh;
2289 	GetFontHeight(&fh);
2290 	frame.Set(0.0f, 0.0f, 0.0f, ceilf(fh.ascent + fh.descent + fPad.top
2291 		+ fPad.bottom));
2292 
2293 	for (int32 i = 0; i < fItems.CountItems(); i++) {
2294 		BMenuItem* item = ItemAt(i);
2295 
2296 		float width, height;
2297 		item->GetContentSize(&width, &height);
2298 
2299 		item->fBounds.left = frame.right;
2300 		item->fBounds.top = 0.0f;
2301 		item->fBounds.right = item->fBounds.left + width + fPad.left
2302 			+ fPad.right;
2303 
2304 		frame.right = item->Frame().right + 1.0f;
2305 		frame.bottom = std::max(frame.bottom, height + fPad.top + fPad.bottom);
2306 	}
2307 
2308 	if (moveItems) {
2309 		for (int32 i = 0; i < fItems.CountItems(); i++)
2310 			ItemAt(i)->fBounds.bottom = frame.bottom;
2311 	}
2312 
2313 	if (bestFit)
2314 		frame.right = ceilf(frame.right);
2315 	else
2316 		frame.right = Bounds().right;
2317 }
2318 
2319 
2320 void
2321 BMenu::_ComputeMatrixLayout(BRect &frame)
2322 {
2323 	frame.Set(0, 0, 0, 0);
2324 	for (int32 i = 0; i < CountItems(); i++) {
2325 		BMenuItem* item = ItemAt(i);
2326 		if (item != NULL) {
2327 			frame.left = std::min(frame.left, item->Frame().left);
2328 			frame.right = std::max(frame.right, item->Frame().right);
2329 			frame.top = std::min(frame.top, item->Frame().top);
2330 			frame.bottom = std::max(frame.bottom, item->Frame().bottom);
2331 		}
2332 	}
2333 }
2334 
2335 
2336 void
2337 BMenu::LayoutInvalidated(bool descendants)
2338 {
2339 	fUseCachedMenuLayout = false;
2340 	fLayoutData->preferred.Set(B_SIZE_UNSET, B_SIZE_UNSET);
2341 }
2342 
2343 
2344 // Assumes the SuperMenu to be locked (due to calling ConvertToScreen())
2345 BPoint
2346 BMenu::ScreenLocation()
2347 {
2348 	BMenu* superMenu = Supermenu();
2349 	BMenuItem* superItem = Superitem();
2350 
2351 	if (superMenu == NULL || superItem == NULL) {
2352 		debugger("BMenu can't determine where to draw."
2353 			"Override BMenu::ScreenLocation() to determine location.");
2354 	}
2355 
2356 	BPoint point;
2357 	if (superMenu->Layout() == B_ITEMS_IN_COLUMN)
2358 		point = superItem->Frame().RightTop() + BPoint(1.0f, 1.0f);
2359 	else
2360 		point = superItem->Frame().LeftBottom() + BPoint(1.0f, 1.0f);
2361 
2362 	superMenu->ConvertToScreen(&point);
2363 
2364 	return point;
2365 }
2366 
2367 
2368 BRect
2369 BMenu::_CalcFrame(BPoint where, bool* scrollOn)
2370 {
2371 	// TODO: Improve me
2372 	BRect bounds = Bounds();
2373 	BRect frame = bounds.OffsetToCopy(where);
2374 
2375 	BScreen screen(Window());
2376 	BRect screenFrame = screen.Frame();
2377 
2378 	BMenu* superMenu = Supermenu();
2379 	BMenuItem* superItem = Superitem();
2380 
2381 	// TODO: Horrible hack:
2382 	// When added to a BMenuField, a BPopUpMenu is the child of
2383 	// a _BMCMenuBar_ to "fake" the menu hierarchy
2384 	bool inMenuField = dynamic_cast<_BMCMenuBar_*>(superMenu) != NULL;
2385 	bool scroll = false;
2386 	if (superMenu == NULL || superItem == NULL || inMenuField) {
2387 		// just move the window on screen
2388 
2389 		if (frame.bottom > screenFrame.bottom)
2390 			frame.OffsetBy(0, screenFrame.bottom - frame.bottom);
2391 		else if (frame.top < screenFrame.top)
2392 			frame.OffsetBy(0, -frame.top);
2393 
2394 		if (frame.right > screenFrame.right)
2395 			frame.OffsetBy(screenFrame.right - frame.right, 0);
2396 		else if (frame.left < screenFrame.left)
2397 			frame.OffsetBy(-frame.left, 0);
2398 	} else if (superMenu->Layout() == B_ITEMS_IN_COLUMN) {
2399 		if (frame.right > screenFrame.right)
2400 			frame.OffsetBy(-superItem->Frame().Width() - frame.Width() - 2, 0);
2401 
2402 		if (frame.left < 0)
2403 			frame.OffsetBy(-frame.left + 6, 0);
2404 
2405 		if (frame.bottom > screenFrame.bottom)
2406 			frame.OffsetBy(0, screenFrame.bottom - frame.bottom);
2407 	} else {
2408 		if (frame.bottom > screenFrame.bottom) {
2409 			if (scrollOn != NULL && superMenu != NULL
2410 				&& dynamic_cast<BMenuBar*>(superMenu) != NULL
2411 				&& frame.top < (screenFrame.bottom - 80)) {
2412 				scroll = true;
2413 			} else {
2414 				frame.OffsetBy(0, -superItem->Frame().Height()
2415 					- frame.Height() - 3);
2416 			}
2417 		}
2418 
2419 		if (frame.right > screenFrame.right)
2420 			frame.OffsetBy(screenFrame.right - frame.right, 0);
2421 	}
2422 
2423 	if (!scroll) {
2424 		// basically, if this returns false, it means
2425 		// that the menu frame won't fit completely inside the screen
2426 		// TODO: Scrolling will currently only work up/down,
2427 		// not left/right
2428 		scroll = screenFrame.Height() < frame.Height();
2429 	}
2430 
2431 	if (scrollOn != NULL)
2432 		*scrollOn = scroll;
2433 
2434 	return frame;
2435 }
2436 
2437 
2438 void
2439 BMenu::_DrawItems(BRect updateRect)
2440 {
2441 	int32 itemCount = fItems.CountItems();
2442 	for (int32 i = 0; i < itemCount; i++) {
2443 		BMenuItem* item = ItemAt(i);
2444 		if (item->Frame().Intersects(updateRect))
2445 			item->Draw();
2446 	}
2447 }
2448 
2449 
2450 int
2451 BMenu::_State(BMenuItem** item) const
2452 {
2453 	if (fState == MENU_STATE_TRACKING || fState == MENU_STATE_CLOSED)
2454 		return fState;
2455 
2456 	if (fSelected != NULL && fSelected->Submenu() != NULL)
2457 		return fSelected->Submenu()->_State(item);
2458 
2459 	return fState;
2460 }
2461 
2462 
2463 void
2464 BMenu::_InvokeItem(BMenuItem* item, bool now)
2465 {
2466 	if (!item->IsEnabled())
2467 		return;
2468 
2469 	// Do the "selected" animation
2470 	// TODO: Doesn't work. This is supposed to highlight
2471 	// and dehighlight the item, works on beos but not on haiku.
2472 	if (!item->Submenu() && LockLooper()) {
2473 		snooze(50000);
2474 		item->Select(true);
2475 		Window()->UpdateIfNeeded();
2476 		snooze(50000);
2477 		item->Select(false);
2478 		Window()->UpdateIfNeeded();
2479 		snooze(50000);
2480 		item->Select(true);
2481 		Window()->UpdateIfNeeded();
2482 		snooze(50000);
2483 		item->Select(false);
2484 		Window()->UpdateIfNeeded();
2485 		UnlockLooper();
2486 	}
2487 
2488 	// Lock the root menu window before calling BMenuItem::Invoke()
2489 	BMenu* parent = this;
2490 	BMenu* rootMenu = NULL;
2491 	do {
2492 		rootMenu = parent;
2493 		parent = rootMenu->Supermenu();
2494 	} while (parent != NULL);
2495 
2496 	if (rootMenu->LockLooper()) {
2497 		item->Invoke();
2498 		rootMenu->UnlockLooper();
2499 	}
2500 }
2501 
2502 
2503 bool
2504 BMenu::_OverSuper(BPoint location)
2505 {
2506 	if (!Supermenu())
2507 		return false;
2508 
2509 	return fSuperbounds.Contains(location);
2510 }
2511 
2512 
2513 bool
2514 BMenu::_OverSubmenu(BMenuItem* item, BPoint loc)
2515 {
2516 	if (item == NULL)
2517 		return false;
2518 
2519 	BMenu* subMenu = item->Submenu();
2520 	if (subMenu == NULL || subMenu->Window() == NULL)
2521 		return false;
2522 
2523 	// assume that loc is in screen coordinates
2524 	if (subMenu->Window()->Frame().Contains(loc))
2525 		return true;
2526 
2527 	return subMenu->_OverSubmenu(subMenu->fSelected, loc);
2528 }
2529 
2530 
2531 BMenuWindow*
2532 BMenu::_MenuWindow()
2533 {
2534 #if USE_CACHED_MENUWINDOW
2535 	if (fCachedMenuWindow == NULL) {
2536 		char windowName[64];
2537 		snprintf(windowName, 64, "%s cached menu", Name());
2538 		fCachedMenuWindow = new (nothrow) BMenuWindow(windowName);
2539 	}
2540 #endif
2541 	return fCachedMenuWindow;
2542 }
2543 
2544 
2545 void
2546 BMenu::_DeleteMenuWindow()
2547 {
2548 	if (fCachedMenuWindow != NULL) {
2549 		fCachedMenuWindow->Lock();
2550 		fCachedMenuWindow->Quit();
2551 		fCachedMenuWindow = NULL;
2552 	}
2553 }
2554 
2555 
2556 BMenuItem*
2557 BMenu::_HitTestItems(BPoint where, BPoint slop) const
2558 {
2559 	// TODO: Take "slop" into account ?
2560 
2561 	// if the point doesn't lie within the menu's
2562 	// bounds, bail out immediately
2563 	if (!Bounds().Contains(where))
2564 		return NULL;
2565 
2566 	int32 itemCount = CountItems();
2567 	for (int32 i = 0; i < itemCount; i++) {
2568 		BMenuItem* item = ItemAt(i);
2569 		if (item->Frame().Contains(where)
2570 			&& dynamic_cast<BSeparatorItem*>(item) == NULL) {
2571 			return item;
2572 		}
2573 	}
2574 
2575 	return NULL;
2576 }
2577 
2578 
2579 BRect
2580 BMenu::_Superbounds() const
2581 {
2582 	return fSuperbounds;
2583 }
2584 
2585 
2586 void
2587 BMenu::_CacheFontInfo()
2588 {
2589 	font_height fh;
2590 	GetFontHeight(&fh);
2591 	fAscent = fh.ascent;
2592 	fDescent = fh.descent;
2593 	fFontHeight = ceilf(fh.ascent + fh.descent + fh.leading);
2594 }
2595 
2596 
2597 void
2598 BMenu::_ItemMarked(BMenuItem* item)
2599 {
2600 	if (IsRadioMode()) {
2601 		for (int32 i = 0; i < CountItems(); i++) {
2602 			if (ItemAt(i) != item)
2603 				ItemAt(i)->SetMarked(false);
2604 		}
2605 	}
2606 
2607 	if (IsLabelFromMarked() && Superitem())
2608 		Superitem()->SetLabel(item->Label());
2609 }
2610 
2611 
2612 void
2613 BMenu::_Install(BWindow* target)
2614 {
2615 	for (int32 i = 0; i < CountItems(); i++)
2616 		ItemAt(i)->Install(target);
2617 }
2618 
2619 
2620 void
2621 BMenu::_Uninstall()
2622 {
2623 	for (int32 i = 0; i < CountItems(); i++)
2624 		ItemAt(i)->Uninstall();
2625 }
2626 
2627 
2628 void
2629 BMenu::_SelectItem(BMenuItem* item, bool showSubmenu, bool selectFirstItem,
2630 	bool keyDown)
2631 {
2632 	// Avoid deselecting and then reselecting the same item
2633 	// which would cause flickering
2634 	if (item != fSelected) {
2635 		if (fSelected != NULL) {
2636 			fSelected->Select(false);
2637 			BMenu* subMenu = fSelected->Submenu();
2638 			if (subMenu != NULL && subMenu->Window() != NULL)
2639 				subMenu->_Hide();
2640 		}
2641 
2642 		fSelected = item;
2643 		if (fSelected != NULL)
2644 			fSelected->Select(true);
2645 	}
2646 
2647 	if (fSelected != NULL && showSubmenu) {
2648 		BMenu* subMenu = fSelected->Submenu();
2649 		if (subMenu != NULL && subMenu->Window() == NULL) {
2650 			if (!subMenu->_Show(selectFirstItem, keyDown)) {
2651 				// something went wrong, deselect the item
2652 				fSelected->Select(false);
2653 				fSelected = NULL;
2654 			}
2655 		}
2656 	}
2657 }
2658 
2659 
2660 bool
2661 BMenu::_SelectNextItem(BMenuItem* item, bool forward)
2662 {
2663 	if (CountItems() == 0) // cannot select next item in an empty menu
2664 		return false;
2665 
2666 	BMenuItem* nextItem = _NextItem(item, forward);
2667 	if (nextItem == NULL)
2668 		return false;
2669 
2670 	_SelectItem(nextItem, dynamic_cast<BMenuBar*>(this) != NULL);
2671 
2672 	if (LockLooper()) {
2673 		be_app->ObscureCursor();
2674 		UnlockLooper();
2675 	}
2676 
2677 	return true;
2678 }
2679 
2680 
2681 BMenuItem*
2682 BMenu::_NextItem(BMenuItem* item, bool forward) const
2683 {
2684 	const int32 numItems = fItems.CountItems();
2685 	if (numItems == 0)
2686 		return NULL;
2687 
2688 	int32 index = fItems.IndexOf(item);
2689 	int32 loopCount = numItems;
2690 	while (--loopCount) {
2691 		// Cycle through menu items in the given direction...
2692 		if (forward)
2693 			index++;
2694 		else
2695 			index--;
2696 
2697 		// ... wrap around...
2698 		if (index < 0)
2699 			index = numItems - 1;
2700 		else if (index >= numItems)
2701 			index = 0;
2702 
2703 		// ... and return the first suitable item found.
2704 		BMenuItem* nextItem = ItemAt(index);
2705 		if (nextItem->IsEnabled())
2706 			return nextItem;
2707 	}
2708 
2709 	// If no other suitable item was found, return NULL.
2710 	return NULL;
2711 }
2712 
2713 
2714 void
2715 BMenu::_SetIgnoreHidden(bool on)
2716 {
2717 	fIgnoreHidden = on;
2718 }
2719 
2720 
2721 void
2722 BMenu::_SetStickyMode(bool on)
2723 {
2724 	if (fStickyMode == on)
2725 		return;
2726 
2727 	fStickyMode = on;
2728 
2729 	if (fSuper != NULL) {
2730 		// propagate the status to the super menu
2731 		fSuper->_SetStickyMode(on);
2732 	} else {
2733 		// TODO: Ugly hack, but it needs to be done in this method
2734 		BMenuBar* menuBar = dynamic_cast<BMenuBar*>(this);
2735 		if (on && menuBar != NULL && menuBar->LockLooper()) {
2736 			// If we are switching to sticky mode,
2737 			// steal the focus from the current focus view
2738 			// (needed to handle keyboard navigation)
2739 			menuBar->_StealFocus();
2740 			menuBar->UnlockLooper();
2741 		}
2742 	}
2743 }
2744 
2745 
2746 bool
2747 BMenu::_IsStickyMode() const
2748 {
2749 	return fStickyMode;
2750 }
2751 
2752 
2753 void
2754 BMenu::_GetShiftKey(uint32 &value) const
2755 {
2756 	// TODO: Move into init_interface_kit().
2757 	// Currently we can't do that, as get_modifier_key() blocks forever
2758 	// when called on input_server initialization, since it tries
2759 	// to send a synchronous message to itself (input_server is
2760 	// a BApplication)
2761 
2762 	if (get_modifier_key(B_LEFT_SHIFT_KEY, &value) != B_OK)
2763 		value = 0x4b;
2764 }
2765 
2766 
2767 void
2768 BMenu::_GetControlKey(uint32 &value) const
2769 {
2770 	// TODO: Move into init_interface_kit().
2771 	// Currently we can't do that, as get_modifier_key() blocks forever
2772 	// when called on input_server initialization, since it tries
2773 	// to send a synchronous message to itself (input_server is
2774 	// a BApplication)
2775 
2776 	if (get_modifier_key(B_LEFT_CONTROL_KEY, &value) != B_OK)
2777 		value = 0x5c;
2778 }
2779 
2780 
2781 void
2782 BMenu::_GetCommandKey(uint32 &value) const
2783 {
2784 	// TODO: Move into init_interface_kit().
2785 	// Currently we can't do that, as get_modifier_key() blocks forever
2786 	// when called on input_server initialization, since it tries
2787 	// to send a synchronous message to itself (input_server is
2788 	// a BApplication)
2789 
2790 	if (get_modifier_key(B_LEFT_COMMAND_KEY, &value) != B_OK)
2791 		value = 0x66;
2792 }
2793 
2794 
2795 void
2796 BMenu::_GetOptionKey(uint32 &value) const
2797 {
2798 	// TODO: Move into init_interface_kit().
2799 	// Currently we can't do that, as get_modifier_key() blocks forever
2800 	// when called on input_server initialization, since it tries
2801 	// to send a synchronous message to itself (input_server is
2802 	// a BApplication)
2803 
2804 	if (get_modifier_key(B_LEFT_OPTION_KEY, &value) != B_OK)
2805 		value = 0x5d;
2806 }
2807 
2808 
2809 void
2810 BMenu::_GetMenuKey(uint32 &value) const
2811 {
2812 	// TODO: Move into init_interface_kit().
2813 	// Currently we can't do that, as get_modifier_key() blocks forever
2814 	// when called on input_server initialization, since it tries
2815 	// to send a synchronous message to itself (input_server is
2816 	// a BApplication)
2817 
2818 	if (get_modifier_key(B_MENU_KEY, &value) != B_OK)
2819 		value = 0x68;
2820 }
2821 
2822 
2823 void
2824 BMenu::_CalcTriggers()
2825 {
2826 	BPrivate::TriggerList triggerList;
2827 
2828 	// Gathers the existing triggers set by the user
2829 	for (int32 i = 0; i < CountItems(); i++) {
2830 		char trigger = ItemAt(i)->Trigger();
2831 		if (trigger != 0)
2832 			triggerList.AddTrigger(trigger);
2833 	}
2834 
2835 	// Set triggers for items which don't have one yet
2836 	for (int32 i = 0; i < CountItems(); i++) {
2837 		BMenuItem* item = ItemAt(i);
2838 		if (item->Trigger() == 0) {
2839 			uint32 trigger;
2840 			int32 index;
2841 			if (_ChooseTrigger(item->Label(), index, trigger, triggerList))
2842 				item->SetAutomaticTrigger(index, trigger);
2843 		}
2844 	}
2845 }
2846 
2847 
2848 bool
2849 BMenu::_ChooseTrigger(const char* title, int32& index, uint32& trigger,
2850 	BPrivate::TriggerList& triggers)
2851 {
2852 	if (title == NULL)
2853 		return false;
2854 
2855 	uint32 c;
2856 
2857 	// two runs: first we look out for uppercase letters
2858 	// TODO: support Unicode characters correctly!
2859 	for (uint32 i = 0; (c = title[i]) != '\0'; i++) {
2860 		if (!IsInsideGlyph(c) && isupper(c) && !triggers.HasTrigger(c)) {
2861 			index = i;
2862 			trigger = tolower(c);
2863 			return triggers.AddTrigger(c);
2864 		}
2865 	}
2866 
2867 	// then, if we still haven't found anything, we accept them all
2868 	index = 0;
2869 	while ((c = UTF8ToCharCode(&title)) != 0) {
2870 		if (!isspace(c) && !triggers.HasTrigger(c)) {
2871 			trigger = tolower(c);
2872 			return triggers.AddTrigger(c);
2873 		}
2874 
2875 		index++;
2876 	}
2877 
2878 	return false;
2879 }
2880 
2881 
2882 void
2883 BMenu::_UpdateWindowViewSize(const bool &move)
2884 {
2885 	BMenuWindow* window = static_cast<BMenuWindow*>(Window());
2886 	if (window == NULL)
2887 		return;
2888 
2889 	if (dynamic_cast<BMenuBar*>(this) != NULL)
2890 		return;
2891 
2892 	if (!fResizeToFit)
2893 		return;
2894 
2895 	bool scroll = false;
2896 	const BPoint screenLocation = move ? ScreenLocation()
2897 		: window->Frame().LeftTop();
2898 	BRect frame = _CalcFrame(screenLocation, &scroll);
2899 	ResizeTo(frame.Width(), frame.Height());
2900 
2901 	if (fItems.CountItems() > 0) {
2902 		if (!scroll) {
2903 			window->ResizeTo(Bounds().Width(), Bounds().Height());
2904 		} else {
2905 			BScreen screen(window);
2906 
2907 			// If we need scrolling, resize the window to fit the screen and
2908 			// attach scrollers to our cached BMenuWindow.
2909 			if (dynamic_cast<BMenuBar*>(Supermenu()) == NULL || frame.top < 0) {
2910 				window->ResizeTo(Bounds().Width(), screen.Frame().Height());
2911 				frame.top = 0;
2912 			} else {
2913 				// Or, in case our parent was a BMenuBar enable scrolling with
2914 				// normal size.
2915 				window->ResizeTo(Bounds().Width(),
2916 					screen.Frame().bottom - frame.top);
2917 			}
2918 
2919 			if (fLayout == B_ITEMS_IN_COLUMN) {
2920 				// we currently only support scrolling for B_ITEMS_IN_COLUMN
2921 				window->AttachScrollers();
2922 
2923 				BMenuItem* selectedItem = FindMarked();
2924 				if (selectedItem != NULL) {
2925 					// scroll to the selected item
2926 					if (Supermenu() == NULL) {
2927 						window->TryScrollTo(selectedItem->Frame().top);
2928 					} else {
2929 						BPoint point = selectedItem->Frame().LeftTop();
2930 						BPoint superPoint = Superitem()->Frame().LeftTop();
2931 						Supermenu()->ConvertToScreen(&superPoint);
2932 						ConvertToScreen(&point);
2933 						window->TryScrollTo(point.y - superPoint.y);
2934 					}
2935 				}
2936 			}
2937 		}
2938 	} else {
2939 		_CacheFontInfo();
2940 		window->ResizeTo(StringWidth(BPrivate::kEmptyMenuLabel)
2941 			+ fPad.left + fPad.right,
2942 			fFontHeight + fPad.top + fPad.bottom);
2943 	}
2944 
2945 	if (move)
2946 		window->MoveTo(frame.LeftTop());
2947 }
2948 
2949 
2950 bool
2951 BMenu::_AddDynamicItems(bool keyDown)
2952 {
2953 	bool addAborted = false;
2954 	if (AddDynamicItem(B_INITIAL_ADD)) {
2955 		BMenuItem* superItem = Superitem();
2956 		BMenu* superMenu = Supermenu();
2957 		do {
2958 			if (superMenu != NULL
2959 				&& !superMenu->_OkToProceed(superItem, keyDown)) {
2960 				AddDynamicItem(B_ABORT);
2961 				addAborted = true;
2962 				break;
2963 			}
2964 		} while (AddDynamicItem(B_PROCESSING));
2965 	}
2966 
2967 	return addAborted;
2968 }
2969 
2970 
2971 bool
2972 BMenu::_OkToProceed(BMenuItem* item, bool keyDown)
2973 {
2974 	BPoint where;
2975 	uint32 buttons;
2976 	GetMouse(&where, &buttons, false);
2977 	bool stickyMode = _IsStickyMode();
2978 	// Quit if user clicks the mouse button in sticky mode
2979 	// or releases the mouse button in nonsticky mode
2980 	// or moves the pointer over another item
2981 	// TODO: I added the check for BMenuBar to solve a problem with Deskbar.
2982 	// BeOS seems to do something similar. This could also be a bug in
2983 	// Deskbar, though.
2984 	if ((buttons != 0 && stickyMode)
2985 		|| ((dynamic_cast<BMenuBar*>(this) == NULL
2986 			&& (buttons == 0 && !stickyMode))
2987 		|| ((_HitTestItems(where) != item) && !keyDown))) {
2988 		return false;
2989 	}
2990 
2991 	return true;
2992 }
2993 
2994 
2995 bool
2996 BMenu::_CustomTrackingWantsToQuit()
2997 {
2998 	if (fExtraMenuData != NULL && fExtraMenuData->trackingHook != NULL
2999 		&& fExtraMenuData->trackingState != NULL) {
3000 		return fExtraMenuData->trackingHook(this,
3001 			fExtraMenuData->trackingState);
3002 	}
3003 
3004 	return false;
3005 }
3006 
3007 
3008 void
3009 BMenu::_QuitTracking(bool onlyThis)
3010 {
3011 	_SelectItem(NULL);
3012 	if (BMenuBar* menuBar = dynamic_cast<BMenuBar*>(this))
3013 		menuBar->_RestoreFocus();
3014 
3015 	fState = MENU_STATE_CLOSED;
3016 
3017 	if (!onlyThis) {
3018 		// Close the whole menu hierarchy
3019 		if (Supermenu() != NULL)
3020 			Supermenu()->fState = MENU_STATE_CLOSED;
3021 
3022 		if (_IsStickyMode())
3023 			_SetStickyMode(false);
3024 
3025 		if (LockLooper()) {
3026 			be_app->ShowCursor();
3027 			UnlockLooper();
3028 		}
3029 	}
3030 
3031 	_Hide();
3032 }
3033 
3034 
3035 //	#pragma mark - menu_info functions
3036 
3037 
3038 // TODO: Maybe the following two methods would fit better into
3039 // InterfaceDefs.cpp
3040 // In R5, they do all the work client side, we let the app_server handle the
3041 // details.
3042 status_t
3043 set_menu_info(menu_info* info)
3044 {
3045 	if (!info)
3046 		return B_BAD_VALUE;
3047 
3048 	BPrivate::AppServerLink link;
3049 	link.StartMessage(AS_SET_MENU_INFO);
3050 	link.Attach<menu_info>(*info);
3051 
3052 	status_t status = B_ERROR;
3053 	if (link.FlushWithReply(status) == B_OK && status == B_OK)
3054 		BMenu::sMenuInfo = *info;
3055 		// Update also the local copy, in case anyone relies on it
3056 
3057 	return status;
3058 }
3059 
3060 
3061 status_t
3062 get_menu_info(menu_info* info)
3063 {
3064 	if (!info)
3065 		return B_BAD_VALUE;
3066 
3067 	BPrivate::AppServerLink link;
3068 	link.StartMessage(AS_GET_MENU_INFO);
3069 
3070 	status_t status = B_ERROR;
3071 	if (link.FlushWithReply(status) == B_OK && status == B_OK)
3072 		link.Read<menu_info>(info);
3073 
3074 	return status;
3075 }
3076 
3077 
3078 extern "C" void
3079 B_IF_GCC_2(InvalidateLayout__5BMenub,_ZN5BMenu16InvalidateLayoutEb)(
3080 	BMenu* menu, bool descendants)
3081 {
3082 	menu->InvalidateLayout();
3083 }
3084