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