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