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