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