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