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