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