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