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