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