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