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