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