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