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