Menu.cpp revision 08d92916672b9c6c4274547f9366485dce31330e
1/*
2 * Copyright 2001-2006, Haiku, Inc.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		Marc Flerackers (mflerackers@androme.be)
7 *		Stefano Ceccherini (burton666@libero.it)
8 */
9
10#include <new>
11#include <string.h>
12
13#include <Debug.h>
14#include <File.h>
15#include <FindDirectory.h>
16#include <Menu.h>
17#include <MenuBar.h>
18#include <MenuItem.h>
19#include <Path.h>
20#include <PropertyInfo.h>
21#include <Screen.h>
22#include <Window.h>
23
24#include <AppServerLink.h>
25#include <BMCPrivate.h>
26#include <MenuPrivate.h>
27#include <MenuWindow.h>
28#include <ServerProtocol.h>
29
30using std::nothrow;
31using BPrivate::BMenuWindow;
32
33
34class _ExtraMenuData_ {
35public:
36	menu_tracking_hook trackingHook;
37	void *trackingState;
38
39	_ExtraMenuData_(menu_tracking_hook func, void *state)
40	{
41		trackingHook = func;
42		trackingState = state;
43	};
44};
45
46
47menu_info BMenu::sMenuInfo;
48bool BMenu::sAltAsCommandKey;
49
50
51static property_info
52sPropList[] = {
53	{ "Enabled", { B_GET_PROPERTY, 0 },
54		{ B_DIRECT_SPECIFIER, 0 }, "Returns true if menu or menu item is enabled; false "
55		"otherwise.",
56		0, { B_BOOL_TYPE }
57	},
58
59	{ "Enabled", { B_SET_PROPERTY, 0 },
60		{ B_DIRECT_SPECIFIER, 0 }, "Enables or disables menu or menu item.",
61		0, { B_BOOL_TYPE }
62	},
63
64	{ "Label", { B_GET_PROPERTY, 0 },
65		{ B_DIRECT_SPECIFIER, 0 }, "Returns the string label of the menu or menu item.",
66		0, { B_STRING_TYPE }
67	},
68
69	{ "Label", { B_SET_PROPERTY, 0 },
70		{ B_DIRECT_SPECIFIER, 0 }, "Sets the string label of the menu or menu item.",
71		0, { B_STRING_TYPE }
72	},
73
74	{ "Mark", { B_GET_PROPERTY, 0 },
75		{ B_DIRECT_SPECIFIER, 0 }, "Returns true if the menu item or the menu's superitem "
76		"is marked; false otherwise.",
77		0, { B_BOOL_TYPE }
78	},
79
80	{ "Mark", { B_SET_PROPERTY, 0 },
81		{ B_DIRECT_SPECIFIER, 0 }, "Marks or unmarks the menu item or the menu's superitem.",
82		0, { B_BOOL_TYPE }
83	},
84
85	{ "Menu", { B_CREATE_PROPERTY, 0 },
86		{ B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 },
87		"Adds a new menu item at the specified index with the text label found in \"data\" "
88		"and the int32 command found in \"what\" (used as the what field in the CMessage "
89		"sent by the item)." , 0, {},
90		{ 	{{{"data", B_STRING_TYPE}}}
91		}
92	},
93
94	{ "Menu", { B_DELETE_PROPERTY, 0 },
95		{ B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 },
96		"Removes the selected menu or menus.", 0, {}
97	},
98
99	{ "Menu", { },
100		{ B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 },
101		"Directs scripting message to the specified menu, first popping the current "
102		"specifier off the stack.", 0, {}
103	},
104
105	{ "MenuItem", { B_COUNT_PROPERTIES, 0 },
106		{ B_DIRECT_SPECIFIER, 0 }, "Counts the number of menu items in the specified menu.",
107		0, { B_INT32_TYPE }
108	},
109
110	{ "MenuItem", { B_CREATE_PROPERTY, 0 },
111		{ B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 },
112		"Adds a new menu item at the specified index with the text label found in \"data\" "
113		"and the int32 command found in \"what\" (used as the what field in the CMessage "
114		"sent by the item).", 0, {},
115		{	{ {{"data", B_STRING_TYPE },
116			{"be:invoke_message", B_MESSAGE_TYPE},
117			{"what", B_INT32_TYPE},
118			{"be:target", B_MESSENGER_TYPE}} }
119		}
120	},
121
122	{ "MenuItem", { B_DELETE_PROPERTY, 0 },
123		{ B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 },
124		"Removes the specified menu item from its parent menu."
125	},
126
127	{ "MenuItem", { B_EXECUTE_PROPERTY, 0 },
128		{ B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 },
129		"Invokes the specified menu item."
130	},
131
132	{ "MenuItem", { },
133		{ B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 },
134		"Directs scripting message to the specified menu, first popping the current "
135		"specifier off the stack."
136	},
137
138	{}
139};
140
141
142const char *kEmptyMenuLabel = "<empty>";
143
144
145BMenu::BMenu(const char *name, menu_layout layout)
146	:	BView(BRect(0, 0, 0, 0), name, 0, B_WILL_DRAW),
147		fChosenItem(NULL),
148		fPad(14.0f, 2.0f, 20.0f, 0.0f),
149		fSelected(NULL),
150		fCachedMenuWindow(NULL),
151		fSuper(NULL),
152		fSuperitem(NULL),
153		fAscent(-1.0f),
154		fDescent(-1.0f),
155		fFontHeight(-1.0f),
156		fState(0),
157		fLayout(layout),
158		fExtraRect(NULL),
159		fMaxContentWidth(0.0f),
160		fInitMatrixSize(NULL),
161		fExtraMenuData(NULL),
162		fTrigger(0),
163		fResizeToFit(true),
164		fUseCachedMenuLayout(false),
165		fEnabled(true),
166		fDynamicName(false),
167		fRadioMode(false),
168		fTrackNewBounds(false),
169		fStickyMode(false),
170		fIgnoreHidden(true),
171		fTriggerEnabled(true),
172		fRedrawAfterSticky(false),
173		fAttachAborted(false)
174{
175	InitData(NULL);
176}
177
178
179BMenu::BMenu(const char *name, float width, float height)
180	:	BView(BRect(0.0f, width, 0.0f, height), name, 0, B_WILL_DRAW),
181		fChosenItem(NULL),
182		fSelected(NULL),
183		fCachedMenuWindow(NULL),
184		fSuper(NULL),
185		fSuperitem(NULL),
186		fAscent(-1.0f),
187		fDescent(-1.0f),
188		fFontHeight(-1.0f),
189		fState(0),
190		fLayout(B_ITEMS_IN_MATRIX),
191		fExtraRect(NULL),
192		fMaxContentWidth(0.0f),
193		fInitMatrixSize(NULL),
194		fExtraMenuData(NULL),
195		fTrigger(0),
196		fResizeToFit(true),
197		fUseCachedMenuLayout(false),
198		fEnabled(true),
199		fDynamicName(false),
200		fRadioMode(false),
201		fTrackNewBounds(false),
202		fStickyMode(false),
203		fIgnoreHidden(true),
204		fTriggerEnabled(true),
205		fRedrawAfterSticky(false),
206		fAttachAborted(false)
207{
208	InitData(NULL);
209}
210
211
212BMenu::~BMenu()
213{
214	DeleteMenuWindow();
215
216	RemoveItems(0, CountItems(), true);
217
218	delete fInitMatrixSize;
219	delete fExtraMenuData;
220}
221
222
223BMenu::BMenu(BMessage *archive)
224	:	BView(archive),
225		fChosenItem(NULL),
226		fPad(14.0f, 2.0f, 20.0f, 0.0f),
227		fSelected(NULL),
228		fCachedMenuWindow(NULL),
229		fSuper(NULL),
230		fSuperitem(NULL),
231		fAscent(-1.0f),
232		fDescent(-1.0f),
233		fFontHeight(-1.0f),
234		fState(0),
235		fLayout(B_ITEMS_IN_ROW),
236		fExtraRect(NULL),
237		fMaxContentWidth(0.0f),
238		fInitMatrixSize(NULL),
239		fExtraMenuData(NULL),
240		fTrigger(0),
241		fResizeToFit(true),
242		fUseCachedMenuLayout(false),
243		fEnabled(true),
244		fDynamicName(false),
245		fRadioMode(false),
246		fTrackNewBounds(false),
247		fStickyMode(false),
248		fIgnoreHidden(true),
249		fTriggerEnabled(true),
250		fRedrawAfterSticky(false),
251		fAttachAborted(false)
252{
253	InitData(archive);
254}
255
256
257BArchivable *
258BMenu::Instantiate(BMessage *data)
259{
260	if (validate_instantiation(data, "BMenu"))
261		return new (nothrow) BMenu(data);
262
263	return NULL;
264}
265
266
267status_t
268BMenu::Archive(BMessage *data, bool deep) const
269{
270	status_t err = BView::Archive(data, deep);
271
272	if (err == B_OK && Layout() != B_ITEMS_IN_ROW)
273		err = data->AddInt32("_layout", Layout());
274	if (err == B_OK)
275		err = data->AddBool("_rsize_to_fit", fResizeToFit);
276	if (err == B_OK)
277		err = data->AddBool("_disable", !IsEnabled());
278	if (err ==  B_OK)
279		err = data->AddBool("_radio", IsRadioMode());
280	if (err == B_OK)
281		err = data->AddBool("_trig_disabled", AreTriggersEnabled());
282	if (err == B_OK)
283		err = data->AddBool("_dyn_label", fDynamicName);
284	if (err == B_OK)
285		err = data->AddFloat("_maxwidth", fMaxContentWidth);
286	if (err == B_OK && deep) {
287		BMenuItem *item = NULL;
288		int32 index = 0;
289		while ((item = ItemAt(index++)) != NULL) {
290			BMessage itemData;
291			item->Archive(&itemData, deep);
292			err = data->AddMessage("_items", &itemData);
293			if (err != B_OK)
294				break;
295			if (fLayout == B_ITEMS_IN_MATRIX) {
296				err = data->AddRect("_i_frames", item->fBounds);
297			}
298		}
299	}
300
301	return err;
302}
303
304
305void
306BMenu::AttachedToWindow()
307{
308	BView::AttachedToWindow();
309
310	sAltAsCommandKey = true;
311	key_map *keys = NULL;
312	char *chars = NULL;
313	get_key_map(&keys, &chars);
314	if (keys == NULL || keys->left_command_key != 0x5d || keys->right_command_key != 0x5f)
315		sAltAsCommandKey = false;
316	free(chars);
317	free(keys);
318
319	BMenuItem *superItem = Superitem();
320	BMenu *superMenu = Supermenu();
321	if (AddDynamicItem(B_INITIAL_ADD)) {
322		do {
323			if (superMenu != NULL && !superMenu->OkToProceed(superItem)) {
324				AddDynamicItem(B_ABORT);
325				fAttachAborted = true;
326				break;
327			}
328		} while (AddDynamicItem(B_PROCESSING));
329	}
330
331	if (!fAttachAborted) {
332		CacheFontInfo();
333		LayoutItems(0);
334		//UpdateWindowViewSize();
335	}
336}
337
338
339void
340BMenu::DetachedFromWindow()
341{
342	BView::DetachedFromWindow();
343}
344
345
346bool
347BMenu::AddItem(BMenuItem *item)
348{
349	return AddItem(item, CountItems());
350}
351
352
353bool
354BMenu::AddItem(BMenuItem *item, int32 index)
355{
356	if (fLayout == B_ITEMS_IN_MATRIX)
357		debugger("BMenu::AddItem(BMenuItem *, int32) this method can only "
358				"be called if the menu layout is not B_ITEMS_IN_MATRIX");
359
360	if (!item || !_AddItem(item, index))
361		return false;
362
363	InvalidateLayout();
364	if (LockLooper()) {
365		if (!Window()->IsHidden()) {
366			LayoutItems(index);
367			Invalidate();
368		}
369		UnlockLooper();
370	}
371	return true;
372}
373
374
375bool
376BMenu::AddItem(BMenuItem *item, BRect frame)
377{
378	if (fLayout != B_ITEMS_IN_MATRIX)
379		debugger("BMenu::AddItem(BMenuItem *, BRect) this method can only "
380			"be called if the menu layout is B_ITEMS_IN_MATRIX");
381
382	if (!item)
383		return false;
384
385	item->fBounds = frame;
386
387	int32 index = CountItems();
388	if (!_AddItem(item, index)) {
389		return false;
390	}
391
392	if (LockLooper()) {
393		if (!Window()->IsHidden()) {
394			LayoutItems(index);
395			Invalidate();
396		}
397		UnlockLooper();
398	}
399
400	return true;
401}
402
403
404bool
405BMenu::AddItem(BMenu *submenu)
406{
407	BMenuItem *item = new (nothrow) BMenuItem(submenu);
408	if (!item)
409		return false;
410
411	if (!AddItem(item, CountItems())) {
412		item->fSubmenu = NULL;
413		delete item;
414		return false;
415	}
416
417	return true;
418}
419
420
421bool
422BMenu::AddItem(BMenu *submenu, int32 index)
423{
424	if (fLayout == B_ITEMS_IN_MATRIX)
425		debugger("BMenu::AddItem(BMenuItem *, int32) this method can only "
426				"be called if the menu layout is not B_ITEMS_IN_MATRIX");
427
428	BMenuItem *item = new (nothrow) BMenuItem(submenu);
429	if (!item)
430		return false;
431
432	if (!AddItem(item, index)) {
433		item->fSubmenu = NULL;
434		delete item;
435		return false;
436	}
437
438	return true;
439}
440
441
442bool
443BMenu::AddItem(BMenu *submenu, BRect frame)
444{
445	if (fLayout != B_ITEMS_IN_MATRIX)
446		debugger("BMenu::AddItem(BMenu *, BRect) this method can only "
447			"be called if the menu layout is B_ITEMS_IN_MATRIX");
448
449	BMenuItem *item = new (nothrow) BMenuItem(submenu);
450	if (!item)
451		return false;
452
453	if (!AddItem(item, frame)) {
454		item->fSubmenu = NULL;
455		delete item;
456		return false;
457	}
458
459	return true;
460}
461
462
463bool
464BMenu::AddList(BList *list, int32 index)
465{
466	// TODO: test this function, it's not documented in the bebook.
467	if (list == NULL)
468		return false;
469
470	bool locked = LockLooper();
471
472	int32 numItems = list->CountItems();
473	for (int32 i = 0; i < numItems; i++) {
474		BMenuItem *item = static_cast<BMenuItem *>(list->ItemAt(i));
475		if (item != NULL) {
476			if (!_AddItem(item, index + i))
477				break;
478		}
479	}
480
481	InvalidateLayout();
482	if (locked && Window() != NULL && !Window()->IsHidden()) {
483		// Make sure we update the layout if needed.
484		LayoutItems(index);
485		//UpdateWindowViewSize();
486		Invalidate();
487	}
488
489	if (locked)
490		UnlockLooper();
491
492	return true;
493}
494
495
496bool
497BMenu::AddSeparatorItem()
498{
499	BMenuItem *item = new (nothrow) BSeparatorItem();
500	if (!item || !AddItem(item, CountItems())) {
501		delete item;
502		return false;
503	}
504
505	return true;
506}
507
508
509bool
510BMenu::RemoveItem(BMenuItem *item)
511{
512	// TODO: Check if item is also deleted
513	return RemoveItems(0, 0, item, false);
514}
515
516
517BMenuItem *
518BMenu::RemoveItem(int32 index)
519{
520	BMenuItem *item = ItemAt(index);
521	if (item != NULL)
522		RemoveItems(0, 0, item, false);
523	return item;
524}
525
526
527bool
528BMenu::RemoveItems(int32 index, int32 count, bool del)
529{
530	return RemoveItems(index, count, NULL, del);
531}
532
533
534bool
535BMenu::RemoveItem(BMenu *submenu)
536{
537	for (int32 i = 0; i < fItems.CountItems(); i++) {
538		if (static_cast<BMenuItem *>(fItems.ItemAtFast(i))->Submenu() == submenu)
539			return RemoveItems(i, 1, NULL, false);
540	}
541
542	return false;
543}
544
545
546int32
547BMenu::CountItems() const
548{
549	return fItems.CountItems();
550}
551
552
553BMenuItem *
554BMenu::ItemAt(int32 index) const
555{
556	return static_cast<BMenuItem *>(fItems.ItemAt(index));
557}
558
559
560BMenu *
561BMenu::SubmenuAt(int32 index) const
562{
563	BMenuItem *item = static_cast<BMenuItem *>(fItems.ItemAt(index));
564	return (item != NULL) ? item->Submenu() : NULL;
565}
566
567
568int32
569BMenu::IndexOf(BMenuItem *item) const
570{
571	return fItems.IndexOf(item);
572}
573
574
575int32
576BMenu::IndexOf(BMenu *submenu) const
577{
578	for (int32 i = 0; i < fItems.CountItems(); i++) {
579		if (ItemAt(i)->Submenu() == submenu)
580			return i;
581	}
582
583	return -1;
584}
585
586
587BMenuItem *
588BMenu::FindItem(const char *label) const
589{
590	BMenuItem *item = NULL;
591
592	for (int32 i = 0; i < CountItems(); i++) {
593		item = ItemAt(i);
594
595		if (item->Label() && strcmp(item->Label(), label) == 0)
596			return item;
597
598		if (item->Submenu() != NULL) {
599			item = item->Submenu()->FindItem(label);
600			if (item != NULL)
601				return item;
602		}
603	}
604
605	return NULL;
606}
607
608
609BMenuItem *
610BMenu::FindItem(uint32 command) const
611{
612	BMenuItem *item = NULL;
613
614	for (int32 i = 0; i < CountItems(); i++) {
615		item = ItemAt(i);
616
617		if (item->Command() == command)
618			return item;
619
620		if (item->Submenu() != NULL) {
621			item = item->Submenu()->FindItem(command);
622			if (item != NULL)
623				return item;
624		}
625	}
626
627	return NULL;
628}
629
630
631status_t
632BMenu::SetTargetForItems(BHandler *handler)
633{
634	status_t status = B_OK;
635	for (int32 i = 0; i < fItems.CountItems(); i++) {
636		status = ItemAt(i)->SetTarget(handler);
637		if (status < B_OK)
638			break;
639	}
640
641	return status;
642}
643
644
645status_t
646BMenu::SetTargetForItems(BMessenger messenger)
647{
648	status_t status = B_OK;
649	for (int32 i = 0; i < fItems.CountItems(); i++) {
650		status = ItemAt(i)->SetTarget(messenger);
651		if (status < B_OK)
652			break;
653	}
654
655	return status;
656}
657
658
659void
660BMenu::SetEnabled(bool enabled)
661{
662	if (fEnabled == enabled)
663		return;
664
665	fEnabled = enabled;
666
667	if (fSuperitem)
668		fSuperitem->SetEnabled(enabled);
669}
670
671
672void
673BMenu::SetRadioMode(bool flag)
674{
675	fRadioMode = flag;
676	if (!flag)
677		SetLabelFromMarked(false);
678}
679
680
681void
682BMenu::SetTriggersEnabled(bool flag)
683{
684	fTriggerEnabled = flag;
685}
686
687
688void
689BMenu::SetMaxContentWidth(float width)
690{
691	fMaxContentWidth = width;
692}
693
694
695void
696BMenu::SetLabelFromMarked(bool flag)
697{
698	fDynamicName = flag;
699	if (flag)
700		SetRadioMode(true);
701}
702
703
704bool
705BMenu::IsLabelFromMarked()
706{
707	return fDynamicName;
708}
709
710
711bool
712BMenu::IsEnabled() const
713{
714	if (!fEnabled)
715		return false;
716
717	return fSuper ? fSuper->IsEnabled() : true ;
718}
719
720
721bool
722BMenu::IsRadioMode() const
723{
724	return fRadioMode;
725}
726
727
728bool
729BMenu::AreTriggersEnabled() const
730{
731	return fTriggerEnabled;
732}
733
734
735bool
736BMenu::IsRedrawAfterSticky() const
737{
738	return fRedrawAfterSticky;
739}
740
741
742float
743BMenu::MaxContentWidth() const
744{
745	return fMaxContentWidth;
746}
747
748
749BMenuItem *
750BMenu::FindMarked()
751{
752	for (int32 i = 0; i < fItems.CountItems(); i++) {
753		BMenuItem *item = ItemAt(i);
754		if (item->IsMarked())
755			return item;
756	}
757
758	return NULL;
759}
760
761
762BMenu *
763BMenu::Supermenu() const
764{
765	return fSuper;
766}
767
768
769BMenuItem *
770BMenu::Superitem() const
771{
772	return fSuperitem;
773}
774
775
776void
777BMenu::MessageReceived(BMessage *msg)
778{
779	BView::MessageReceived(msg);
780}
781
782
783void
784BMenu::KeyDown(const char *bytes, int32 numBytes)
785{
786	// TODO: Test how it works on beos and implement it correctly
787	switch (bytes[0]) {
788		case B_UP_ARROW:
789			if (fLayout == B_ITEMS_IN_COLUMN)
790				SelectNextItem(fSelected, false);
791			break;
792
793		case B_DOWN_ARROW:
794			if (fLayout == B_ITEMS_IN_COLUMN)
795				SelectNextItem(fSelected, true);
796			break;
797
798		case B_LEFT_ARROW:
799			if (fLayout == B_ITEMS_IN_ROW)
800				SelectNextItem(fSelected, false);
801			break;
802
803		case B_RIGHT_ARROW:
804			if (fLayout == B_ITEMS_IN_ROW)
805				SelectNextItem(fSelected, true);
806			break;
807
808		case B_ENTER:
809		case B_SPACE:
810			if (fSelected)
811				InvokeItem(fSelected);
812
813			break;
814
815		case B_ESCAPE:
816			QuitTracking();
817			break;
818
819		default:
820			BView::KeyDown(bytes, numBytes);
821	}
822}
823
824
825void
826BMenu::Draw(BRect updateRect)
827{
828	if (RelayoutIfNeeded()) {
829		Invalidate();
830		return;
831	}
832
833	DrawBackground(updateRect);
834	DrawItems(updateRect);
835}
836
837
838void
839BMenu::GetPreferredSize(float *_width, float *_height)
840{
841	ComputeLayout(0, true, false, _width, _height);
842}
843
844
845void
846BMenu::ResizeToPreferred()
847{
848	BView::ResizeToPreferred();
849}
850
851
852void
853BMenu::FrameMoved(BPoint new_position)
854{
855	BView::FrameMoved(new_position);
856}
857
858
859void
860BMenu::FrameResized(float new_width, float new_height)
861{
862	BView::FrameResized(new_width, new_height);
863}
864
865
866void
867BMenu::InvalidateLayout()
868{
869	fUseCachedMenuLayout = false;
870}
871
872
873BHandler *
874BMenu::ResolveSpecifier(BMessage *msg, int32 index, BMessage *specifier,
875						int32 form, const char *property)
876{
877	BPropertyInfo propInfo(sPropList);
878	BHandler *target = NULL;
879
880	switch (propInfo.FindMatch(msg, 0, specifier, form, property)) {
881		case B_ERROR:
882			break;
883
884		case 0:
885		case 1:
886		case 2:
887		case 3:
888		case 4:
889		case 5:
890		case 6:
891		case 7:
892			target = this;
893			break;
894		case 8:
895			// TODO: redirect to menu
896			target = this;
897			break;
898		case 9:
899		case 10:
900		case 11:
901		case 12:
902			target = this;
903			break;
904		case 13:
905			// TODO: redirect to menuitem
906			target = this;
907			break;
908	}
909
910	if (!target)
911		target = BView::ResolveSpecifier(msg, index, specifier, form,
912		property);
913
914	return target;
915}
916
917
918status_t
919BMenu::GetSupportedSuites(BMessage *data)
920{
921	if (data == NULL)
922		return B_BAD_VALUE;
923
924	status_t err = data->AddString("suites", "suite/vnd.Be-menu");
925
926	if (err < B_OK)
927		return err;
928
929	BPropertyInfo propertyInfo(sPropList);
930	err = data->AddFlat("messages", &propertyInfo);
931
932	if (err < B_OK)
933		return err;
934
935	return BView::GetSupportedSuites(data);
936}
937
938
939status_t
940BMenu::Perform(perform_code d, void *arg)
941{
942	return BView::Perform(d, arg);
943}
944
945
946void
947BMenu::MakeFocus(bool focused)
948{
949	BView::MakeFocus(focused);
950}
951
952
953void
954BMenu::AllAttached()
955{
956	BView::AllAttached();
957}
958
959
960void
961BMenu::AllDetached()
962{
963	BView::AllDetached();
964}
965
966
967BMenu::BMenu(BRect frame, const char *name, uint32 resizingMode, uint32 flags,
968			 menu_layout layout, bool resizeToFit)
969	:	BView(frame, name, resizingMode, flags),
970		fChosenItem(NULL),
971		fSelected(NULL),
972		fCachedMenuWindow(NULL),
973		fSuper(NULL),
974		fSuperitem(NULL),
975		fAscent(-1.0f),
976		fDescent(-1.0f),
977		fFontHeight(-1.0f),
978		fState(0),
979		fLayout(layout),
980		fExtraRect(NULL),
981		fMaxContentWidth(0.0f),
982		fInitMatrixSize(NULL),
983		fExtraMenuData(NULL),
984		fTrigger(0),
985		fResizeToFit(resizeToFit),
986		fUseCachedMenuLayout(false),
987		fEnabled(true),
988		fDynamicName(false),
989		fRadioMode(false),
990		fTrackNewBounds(false),
991		fStickyMode(false),
992		fIgnoreHidden(true),
993		fTriggerEnabled(true),
994		fRedrawAfterSticky(false),
995		fAttachAborted(false)
996{
997	InitData(NULL);
998}
999
1000
1001void
1002BMenu::SetItemMargins(float left, float top, float right, float bottom)
1003{
1004	fPad.Set(left, top, right, bottom);
1005}
1006
1007
1008void
1009BMenu::GetItemMargins(float *left, float *top, float *right,
1010						   float *bottom) const
1011{
1012	if (left != NULL)
1013		*left = fPad.left;
1014	if (top != NULL)
1015		*top = fPad.top;
1016	if (right != NULL)
1017		*right = fPad.right;
1018	if (bottom != NULL)
1019		*bottom = fPad.bottom;
1020}
1021
1022
1023menu_layout
1024BMenu::Layout() const
1025{
1026	return fLayout;
1027}
1028
1029
1030void
1031BMenu::Show()
1032{
1033	Show(false);
1034}
1035
1036
1037void
1038BMenu::Show(bool selectFirst)
1039{
1040	Install(NULL);
1041	_show(selectFirst);
1042}
1043
1044
1045void
1046BMenu::Hide()
1047{
1048	_hide();
1049	Uninstall();
1050}
1051
1052
1053BMenuItem *
1054BMenu::Track(bool sticky, BRect *clickToOpenRect)
1055{
1056	if (sticky && LockLooper()) {
1057		RedrawAfterSticky(Bounds());
1058		UnlockLooper();
1059	}
1060
1061	if (clickToOpenRect != NULL && LockLooper()) {
1062		fExtraRect = clickToOpenRect;
1063		ConvertFromScreen(fExtraRect);
1064		UnlockLooper();
1065	}
1066
1067	// If sticky is false, pass 0 to the tracking function
1068	// so the menu will stay in nonsticky mode
1069	const bigtime_t trackTime = sticky ? system_time() : 0;
1070	int action;
1071	BMenuItem *menuItem = _track(&action, trackTime);
1072
1073	SetStickyMode(false);
1074	fExtraRect = NULL;
1075
1076	return menuItem;
1077}
1078
1079
1080bool
1081BMenu::AddDynamicItem(add_state s)
1082{
1083	// Implemented in subclasses
1084	return false;
1085}
1086
1087
1088void
1089BMenu::DrawBackground(BRect update)
1090{
1091	rgb_color oldColor = HighColor();
1092	SetHighColor(sMenuInfo.background_color);
1093	FillRect(Bounds() & update, B_SOLID_HIGH);
1094	SetHighColor(oldColor);
1095}
1096
1097
1098void
1099BMenu::SetTrackingHook(menu_tracking_hook func, void *state)
1100{
1101	delete fExtraMenuData;
1102	fExtraMenuData = new (nothrow) _ExtraMenuData_(func, state);
1103}
1104
1105
1106void BMenu::_ReservedMenu3() {}
1107void BMenu::_ReservedMenu4() {}
1108void BMenu::_ReservedMenu5() {}
1109void BMenu::_ReservedMenu6() {}
1110
1111
1112BMenu &
1113BMenu::operator=(const BMenu &)
1114{
1115	return *this;
1116}
1117
1118
1119void
1120BMenu::InitData(BMessage *data)
1121{
1122	// TODO: Get _color, _fname, _fflt from the message, if present
1123	BFont font;
1124	font.SetFamilyAndStyle(sMenuInfo.f_family, sMenuInfo.f_style);
1125	font.SetSize(sMenuInfo.font_size);
1126	SetFont(&font, B_FONT_FAMILY_AND_STYLE | B_FONT_SIZE);
1127
1128	SetLowColor(sMenuInfo.background_color);
1129	SetViewColor(sMenuInfo.background_color);
1130
1131	if (data != NULL) {
1132		data->FindInt32("_layout", (int32 *)&fLayout);
1133		data->FindBool("_rsize_to_fit", &fResizeToFit);
1134		bool disabled;
1135		if (data->FindBool("_disable", &disabled) == B_OK)
1136			fEnabled = !disabled;
1137		data->FindBool("_radio", &fRadioMode);
1138
1139		bool disableTrigger = false;
1140		data->FindBool("_trig_disabled", &disableTrigger);
1141		fTriggerEnabled = !disableTrigger;
1142
1143		data->FindBool("_dyn_label", &fDynamicName);
1144		data->FindFloat("_maxwidth", &fMaxContentWidth);
1145
1146		BMessage msg;
1147        	for (int32 i = 0; data->FindMessage("_items", i, &msg) == B_OK; i++) {
1148			BArchivable *object = instantiate_object(&msg);
1149			if (BMenuItem *item = dynamic_cast<BMenuItem *>(object)) {
1150				BRect bounds;
1151				if ((fLayout == B_ITEMS_IN_MATRIX)
1152					&& (data->FindRect("_i_frames", i, &bounds) == B_OK))
1153						AddItem(item, bounds);
1154				else
1155					AddItem(item);
1156			}
1157		}
1158	}
1159}
1160
1161
1162bool
1163BMenu::_show(bool selectFirstItem)
1164{
1165	// See if the supermenu has a cached menuwindow,
1166	// and use that one if possible.
1167	BMenuWindow *window = NULL;
1168	bool ourWindow = false;
1169	if (fSuper != NULL) {
1170		fSuperbounds = fSuper->ConvertToScreen(fSuper->Bounds());
1171		window = fSuper->MenuWindow();
1172	}
1173
1174	// Otherwise, create a new one
1175	// This happens for "stand alone" BPopUpMenus
1176	// (i.e. not within a BMenuField)
1177	if (window == NULL) {
1178		// Menu windows get the BMenu's handler name
1179		window = new (nothrow) BMenuWindow(Name());
1180		ourWindow = true;
1181	}
1182
1183	if (window == NULL)
1184		return false;
1185
1186	if (window->Lock()) {
1187		fAttachAborted = false;
1188		window->AttachMenu(this);
1189
1190		// Menu didn't have the time to add its items: aborting...
1191		if (fAttachAborted) {
1192			window->DetachMenu();
1193			// TODO: Probably not needed, we can just let _hide() quit the window
1194			if (ourWindow)
1195				window->Quit();
1196			else
1197				window->Unlock();
1198			return false;
1199		}
1200
1201		// Move the BMenu to 1, 1, if it's attached to a BMenuWindow,
1202		// (that means it's a BMenu, BMenuBars are attached to regular BWindows).
1203		// This is needed to be able to draw the frame around the BMenu.
1204		if (dynamic_cast<BMenuWindow *>(window) != NULL)
1205			MoveTo(1, 1);
1206
1207		UpdateWindowViewSize();
1208		window->Show();
1209
1210		if (selectFirstItem)
1211			_SelectItem(ItemAt(0));
1212
1213		window->Unlock();
1214	}
1215
1216	return true;
1217}
1218
1219
1220void
1221BMenu::_hide()
1222{
1223	BMenuWindow *window = static_cast<BMenuWindow *>(Window());
1224	if (window == NULL || !window->Lock())
1225		return;
1226
1227	if (fSelected != NULL)
1228		_SelectItem(NULL);
1229
1230	window->Hide();
1231	window->DetachMenu();
1232		// we don't want to be deleted when the window is removed
1233
1234	// Delete the menu window used by our submenus
1235	DeleteMenuWindow();
1236
1237	window->Unlock();
1238
1239	if (fSuper == NULL && window->Lock())
1240		window->Quit();
1241}
1242
1243
1244const bigtime_t kHysteresis = 200000; // TODO: Test and reduce if needed.
1245
1246
1247BMenuItem *
1248BMenu::_track(int *action, bigtime_t trackTime, long start)
1249{
1250	// TODO: cleanup
1251	BMenuItem *item = NULL;
1252	bigtime_t openTime = system_time();
1253	bigtime_t closeTime = 0;
1254
1255	fState = MENU_STATE_TRACKING;
1256	if (fSuper != NULL)
1257		fSuper->fState = MENU_STATE_TRACKING_SUBMENU;
1258
1259	while (true) {
1260		if (fExtraMenuData != NULL && fExtraMenuData->trackingHook != NULL
1261			&& fExtraMenuData->trackingState != NULL) {
1262			bool quit = fExtraMenuData->trackingHook(this, fExtraMenuData->trackingState);
1263			if (quit)
1264				break;
1265		}
1266
1267		bool locked = LockLooper();
1268		if (!locked)
1269			break;
1270
1271		bigtime_t snoozeAmount = 50000;
1272		BPoint location;
1273		ulong buttons;
1274		GetMouse(&location, &buttons, true);
1275
1276		Window()->UpdateIfNeeded();
1277		BPoint screenLocation = ConvertToScreen(location);
1278		item = HitTestItems(location, B_ORIGIN);
1279		if (item != NULL) {
1280			if (item != fSelected && system_time() > closeTime + kHysteresis) {
1281				_SelectItem(item, false);
1282				openTime = system_time();
1283			} else if (system_time() > kHysteresis + openTime && item->Submenu() != NULL
1284				&& item->Submenu()->Window() == NULL) {
1285				// Open the submenu if it's not opened yet, but only if
1286				// the mouse pointer stayed over there for some time
1287				// (hysteresis)
1288				_SelectItem(item);
1289				closeTime = system_time();
1290			}
1291			fState = MENU_STATE_TRACKING;
1292		}
1293
1294		// Track the submenu
1295		if (fSelected != NULL && OverSubmenu(fSelected, screenLocation)) {
1296			UnlockLooper();
1297			locked = false;
1298			int submenuAction = MENU_STATE_TRACKING;
1299			BMenu *submenu = fSelected->Submenu();
1300			bool wasSticky = IsStickyMode();
1301			if (wasSticky)
1302				submenu->SetStickyMode(true);
1303			BMenuItem *submenuItem = submenu->_track(&submenuAction, trackTime);
1304
1305			// check if the user started holding down a mouse button in a submenu
1306			if (wasSticky && !IsStickyMode()) {
1307				buttons = 1;
1308					// buttons must have been pressed in the meantime
1309				trackTime = 0;
1310					// we are already in non-sticky mode
1311			}
1312
1313			//submenu->Window()->Activate();
1314			if (submenuAction == MENU_STATE_CLOSED) {
1315				item = submenuItem;
1316				fState = submenuAction;
1317				break;
1318			}
1319
1320			locked = LockLooper();
1321			if (!locked)
1322				break;
1323		}
1324
1325		if (item == NULL) {
1326			if (OverSuper(screenLocation)) {
1327				fState = MENU_STATE_TRACKING;
1328				UnlockLooper();
1329				break;
1330			}
1331
1332			if (fSelected != NULL && !OverSubmenu(fSelected, screenLocation)
1333				&& system_time() > closeTime + kHysteresis
1334				&& fState != MENU_STATE_TRACKING_SUBMENU) {
1335				_SelectItem(NULL);
1336				fState = MENU_STATE_TRACKING;
1337			}
1338
1339			if (fSuper != NULL) {
1340				if (locked)
1341					UnlockLooper();
1342				*action = fState;
1343				return NULL;
1344			}
1345		}
1346
1347		if (locked)
1348			UnlockLooper();
1349
1350		if (buttons != 0 && IsStickyMode()) {
1351			if (item == NULL)
1352				fState = MENU_STATE_CLOSED;
1353			else {
1354				BMenu *supermenu = Supermenu();
1355				for(; supermenu; supermenu = supermenu->Supermenu())
1356					supermenu->SetStickyMode(false);
1357				SetStickyMode(false);
1358				trackTime = 0;
1359			}
1360		} else if (buttons == 0 && !IsStickyMode()) {
1361/*			TODO: FIXME! trackTime is a hacky workaround for BMenuField. It
1362			opens directly under your mouse pointer, so when you release the mouse
1363			button the menu closes again because it started in non-sticky mode. */
1364/*			if (system_time() < trackTime + 1000000
1365				|| (fExtraRect != NULL && fExtraRect->Contains(location))) */
1366			if (fExtraRect != NULL && fExtraRect->Contains(location))
1367				SetStickyMode(true);
1368			else
1369				fState = MENU_STATE_CLOSED;
1370		}
1371
1372		if (fState == MENU_STATE_CLOSED)
1373			break;
1374
1375		snooze(snoozeAmount);
1376	}
1377
1378	if (action != NULL)
1379		*action = fState;
1380
1381	if (fSelected != NULL && LockLooper()) {
1382		_SelectItem(NULL);
1383		UnlockLooper();
1384	}
1385
1386	if (IsStickyMode())
1387		SetStickyMode(false);
1388
1389	// delete the menu window recycled for all the child menus
1390	DeleteMenuWindow();
1391
1392	return item;
1393}
1394
1395
1396bool
1397BMenu::_AddItem(BMenuItem *item, int32 index)
1398{
1399	ASSERT(item != NULL);
1400	if (index < 0 || index > fItems.CountItems())
1401		return false;
1402
1403	if (!fItems.AddItem(item, index))
1404		return false;
1405
1406	// install the item on the supermenu's window
1407	// or onto our window, if we are a root menu
1408	BWindow* window = NULL;
1409	if (Superitem() != NULL)
1410		window = Superitem()->fWindow;
1411	else
1412		window = Window();
1413	if (window != NULL)
1414		item->Install(window);
1415
1416	item->SetSuper(this);
1417
1418	return true;
1419}
1420
1421
1422bool
1423BMenu::RemoveItems(int32 index, int32 count, BMenuItem *item, bool deleteItems)
1424{
1425	bool success = false;
1426	bool invalidateLayout = false;
1427
1428	bool locked = LockLooper();
1429	BWindow *window = Window();
1430
1431	// The plan is simple: If we're given a BMenuItem directly, we use it
1432	// and ignore index and count. Otherwise, we use them instead.
1433	if (item != NULL) {
1434		if (fItems.RemoveItem(item)) {
1435			if (item == fSelected && window != NULL)
1436				_SelectItem(NULL);
1437			item->Uninstall();
1438			item->SetSuper(NULL);
1439			if (deleteItems)
1440				delete item;
1441			success = invalidateLayout = true;
1442		}
1443	} else {
1444		// We iterate backwards because it's simpler
1445		int32 i = min_c(index + count - 1, fItems.CountItems() - 1);
1446		// NOTE: the range check for "index" is done after
1447		// calculating the last index to be removed, so
1448		// that the range is not "shifted" unintentionally
1449		index = max_c(0, index);
1450		for (; i >= index; i--) {
1451			item = static_cast<BMenuItem*>(fItems.ItemAt(i));
1452			if (item != NULL) {
1453				if (fItems.RemoveItem(item)) {
1454					if (item == fSelected && window != NULL)
1455						_SelectItem(NULL);
1456					item->Uninstall();
1457					item->SetSuper(NULL);
1458					if (deleteItems)
1459						delete item;
1460					success = true;
1461					invalidateLayout = true;
1462				} else {
1463					// operation not entirely successful
1464					success = false;
1465					break;
1466				}
1467			}
1468		}
1469	}
1470
1471	if (invalidateLayout && locked && window != NULL) {
1472		LayoutItems(0);
1473		//UpdateWindowViewSize();
1474		Invalidate();
1475	}
1476
1477	if (locked)
1478		UnlockLooper();
1479
1480	return success;
1481}
1482
1483
1484bool
1485BMenu::RelayoutIfNeeded()
1486{
1487	if (!fUseCachedMenuLayout) {
1488		fUseCachedMenuLayout = true;
1489		CacheFontInfo();
1490		LayoutItems(0);
1491		return true;
1492	}
1493	return false;
1494}
1495
1496
1497void
1498BMenu::LayoutItems(int32 index)
1499{
1500	CalcTriggers();
1501
1502	float width, height;
1503	ComputeLayout(index, fResizeToFit, true, &width, &height);
1504
1505	ResizeTo(width, height);
1506}
1507
1508
1509void
1510BMenu::ComputeLayout(int32 index, bool bestFit, bool moveItems,
1511	float* _width, float* _height)
1512{
1513	// TODO: Take "bestFit", "moveItems", "index" into account,
1514	// Recalculate only the needed items,
1515	// not the whole layout every time
1516
1517	BRect frame(0, 0, 0, 0);
1518	float iWidth, iHeight;
1519	BMenuItem *item = NULL;
1520
1521	BFont font;
1522	GetFont(&font);
1523	switch (fLayout) {
1524		case B_ITEMS_IN_COLUMN:
1525		{
1526			for (int32 i = 0; i < fItems.CountItems(); i++) {
1527				item = ItemAt(i);
1528				if (item != NULL) {
1529					item->GetContentSize(&iWidth, &iHeight);
1530
1531					if (item->fModifiers && item->fShortcutChar)
1532						iWidth += 2 * font.Size();
1533					if (item->fSubmenu != NULL)
1534						iWidth += 2 * font.Size();
1535
1536					item->fBounds.left = 0.0f;
1537					item->fBounds.top = frame.bottom;
1538					item->fBounds.bottom = item->fBounds.top + iHeight + fPad.top + fPad.bottom;
1539
1540					frame.right = max_c(frame.right, iWidth + fPad.left + fPad.right);
1541					frame.bottom = item->fBounds.bottom + 1.0f;
1542				}
1543			}
1544			if (fMaxContentWidth > 0)
1545				frame.right = min_c(frame.right, fMaxContentWidth);
1546
1547			if (moveItems) {
1548				for (int32 i = 0; i < fItems.CountItems(); i++)
1549					ItemAt(i)->fBounds.right = frame.right;
1550			}
1551			frame.right = ceilf(frame.right);
1552			frame.bottom--;
1553			break;
1554		}
1555
1556		case B_ITEMS_IN_ROW:
1557		{
1558			font_height fh;
1559			GetFontHeight(&fh);
1560			frame = BRect(0.0f, 0.0f, 0.0f,	ceilf(fh.ascent + fh.descent + fPad.top + fPad.bottom));
1561
1562			for (int32 i = 0; i < fItems.CountItems(); i++) {
1563				item = ItemAt(i);
1564				if (item != NULL) {
1565					item->GetContentSize(&iWidth, &iHeight);
1566
1567					item->fBounds.left = frame.right;
1568					item->fBounds.top = 0.0f;
1569					item->fBounds.right = item->fBounds.left + iWidth + fPad.left + fPad.right;
1570
1571					frame.right = item->Frame().right + 1.0f;
1572					frame.bottom = max_c(frame.bottom, iHeight + fPad.top + fPad.bottom);
1573				}
1574			}
1575
1576			if (moveItems) {
1577				for (int32 i = 0; i < fItems.CountItems(); i++)
1578					ItemAt(i)->fBounds.bottom = frame.bottom;
1579			}
1580
1581			if (bestFit)
1582				frame.right = ceilf(frame.right);
1583			else
1584				frame.right = Bounds().right;
1585			break;
1586		}
1587
1588		case B_ITEMS_IN_MATRIX:
1589		{
1590			for (int32 i = 0; i < CountItems(); i++) {
1591				item = ItemAt(i);
1592				if (item != NULL) {
1593					frame.left = min_c(frame.left, item->Frame().left);
1594					frame.right = max_c(frame.right, item->Frame().right);
1595					frame.top = min_c(frame.top, item->Frame().top);
1596					frame.bottom = max_c(frame.bottom, item->Frame().bottom);
1597				}
1598			}
1599			break;
1600		}
1601
1602		default:
1603			break;
1604	}
1605
1606	if (_width) {
1607		// change width depending on resize mode
1608		if ((ResizingMode() & B_FOLLOW_LEFT_RIGHT) == B_FOLLOW_LEFT_RIGHT) {
1609			if (Parent())
1610				*_width = Parent()->Frame().Width() + 1;
1611			else if (Window())
1612				*_width = Window()->Frame().Width() + 1;
1613			else
1614				*_width = Bounds().Width();
1615		} else
1616			*_width = frame.Width();
1617	}
1618
1619	if (_height)
1620		*_height = frame.Height();
1621
1622	if (moveItems)
1623		fUseCachedMenuLayout = true;
1624}
1625
1626
1627BRect
1628BMenu::Bump(BRect current, BPoint extent, int32 index) const
1629{
1630	// ToDo: what's this?
1631	return BRect();
1632}
1633
1634
1635BPoint
1636BMenu::ItemLocInRect(BRect frame) const
1637{
1638	// ToDo: what's this?
1639	return BPoint();
1640}
1641
1642
1643BPoint
1644BMenu::ScreenLocation()
1645{
1646	BMenu *superMenu = Supermenu();
1647	BMenuItem *superItem = Superitem();
1648
1649	if (superMenu == NULL || superItem == NULL) {
1650		debugger("BMenu can't determine where to draw."
1651			"Override BMenu::ScreenLocation() to determine location.");
1652	}
1653
1654	BPoint point;
1655	if (superMenu->Layout() == B_ITEMS_IN_COLUMN)
1656		point = superItem->Frame().RightTop() + BPoint(1.0f, 1.0f);
1657	else
1658		point = superItem->Frame().LeftBottom() + BPoint(1.0f, 1.0f);
1659
1660	superMenu->ConvertToScreen(&point);
1661
1662	return point;
1663}
1664
1665
1666BRect
1667BMenu::CalcFrame(BPoint where, bool *scrollOn)
1668{
1669	// TODO: Improve me
1670	BRect bounds = Bounds();
1671	BRect frame = bounds.OffsetToCopy(where);
1672
1673	BScreen screen(Window());
1674	BRect screenFrame = screen.Frame();
1675
1676	BMenu *superMenu = Supermenu();
1677	BMenuItem *superItem = Superitem();
1678
1679	if (scrollOn != NULL) {
1680		// basically, if this returns false, it means
1681		// that the menu frame won't fit completely inside the screen
1682		*scrollOn = !screenFrame.Contains(bounds);
1683	}
1684
1685	// TODO: Horrible hack:
1686	// When added to a BMenuField, a BPopUpMenu is the child of
1687	// a _BMCMenuBar_ to "fake" the menu hierarchy
1688	if (superMenu == NULL || superItem == NULL
1689		|| dynamic_cast<_BMCMenuBar_ *>(superMenu) != NULL) {
1690		// just move the window on screen
1691
1692		if (frame.bottom > screenFrame.bottom)
1693			frame.OffsetBy(0, screenFrame.bottom - frame.bottom);
1694		else if (frame.top < screenFrame.top)
1695			frame.OffsetBy(0, -frame.top);
1696
1697		if (frame.right > screenFrame.right)
1698			frame.OffsetBy(screenFrame.right - frame.right, 0);
1699		else if (frame.left < screenFrame.left)
1700			frame.OffsetBy(-frame.left, 0);
1701
1702		return frame;
1703	}
1704
1705	if (superMenu->Layout() == B_ITEMS_IN_COLUMN) {
1706		if (frame.right > screenFrame.right)
1707			frame.OffsetBy(-superItem->Frame().Width() - frame.Width() - 2, 0);
1708
1709		if (frame.left < 0)
1710			frame.OffsetBy(-frame.left + 6, 0);
1711
1712		if (frame.bottom > screenFrame.bottom)
1713			frame.OffsetBy(0, screenFrame.bottom - frame.bottom);
1714	} else {
1715		if (frame.bottom > screenFrame.bottom) {
1716			if (scrollOn != NULL && superMenu != NULL &&
1717				dynamic_cast<BMenuBar *>(superMenu) != NULL &&
1718				frame.top < (screenFrame.bottom - 80)) {
1719				*scrollOn = true;
1720			} else {
1721				frame.OffsetBy(0, -superItem->Frame().Height() - frame.Height() - 3);
1722			}
1723		}
1724
1725		if (frame.right > screenFrame.right)
1726			frame.OffsetBy(screenFrame.right - frame.right, 0);
1727	}
1728
1729	return frame;
1730}
1731
1732
1733bool
1734BMenu::ScrollMenu(BRect bounds, BPoint loc, bool *fast)
1735{
1736	return false;
1737}
1738
1739
1740void
1741BMenu::ScrollIntoView(BMenuItem *item)
1742{
1743}
1744
1745
1746void
1747BMenu::DrawItems(BRect updateRect)
1748{
1749	int32 itemCount = fItems.CountItems();
1750	for (int32 i = 0; i < itemCount; i++) {
1751		BMenuItem *item = ItemAt(i);
1752		if (item->Frame().Intersects(updateRect))
1753			item->Draw();
1754	}
1755}
1756
1757
1758int
1759BMenu::State(BMenuItem **item) const
1760{
1761	return 0;
1762}
1763
1764
1765void
1766BMenu::InvokeItem(BMenuItem *item, bool now)
1767{
1768	if (!item->IsEnabled())
1769		return;
1770
1771	// Do the "selected" animation
1772	if (!item->Submenu() && LockLooper()) {
1773		snooze(50000);
1774		item->Select(true);
1775		Sync();
1776		snooze(50000);
1777		item->Select(false);
1778		Sync();
1779		snooze(50000);
1780		item->Select(true);
1781		Sync();
1782		snooze(50000);
1783		item->Select(false);
1784		Sync();
1785		UnlockLooper();
1786	}
1787
1788	item->Invoke();
1789}
1790
1791
1792bool
1793BMenu::OverSuper(BPoint location)
1794{
1795	if (!Supermenu())
1796		return false;
1797
1798	return fSuperbounds.Contains(location);
1799}
1800
1801
1802bool
1803BMenu::OverSubmenu(BMenuItem *item, BPoint loc)
1804{
1805	// we assume that loc is in screen coords
1806	BMenu *subMenu = item->Submenu();
1807	if (subMenu == NULL || subMenu->Window() == NULL)
1808		return false;
1809
1810	if (subMenu->Window()->Frame().Contains(loc))
1811		return true;
1812
1813	if (subMenu->fSelected == NULL)
1814		return false;
1815
1816	return subMenu->OverSubmenu(subMenu->fSelected, loc);
1817}
1818
1819
1820BMenuWindow *
1821BMenu::MenuWindow()
1822{
1823	if (fCachedMenuWindow == NULL) {
1824		char windowName[64];
1825		snprintf(windowName, 64, "%s cached menu", Name());
1826		fCachedMenuWindow = new (nothrow) BMenuWindow(windowName);
1827	}
1828
1829	return fCachedMenuWindow;
1830}
1831
1832
1833void
1834BMenu::DeleteMenuWindow()
1835{
1836	if (fCachedMenuWindow != NULL) {
1837		fCachedMenuWindow->Lock();
1838		fCachedMenuWindow->Quit();
1839		fCachedMenuWindow = NULL;
1840	}
1841}
1842
1843
1844BMenuItem *
1845BMenu::HitTestItems(BPoint where, BPoint slop) const
1846{
1847	// TODO: Take "slop" into account ?
1848
1849	// if the point doesn't lie within the menu's
1850	// bounds, bail out immediately
1851	if (!Bounds().Contains(where))
1852		return NULL;
1853
1854	int32 itemCount = CountItems();
1855	for (int32 i = 0; i < itemCount; i++) {
1856		BMenuItem *item = ItemAt(i);
1857		if (item->Frame().Contains(where))
1858			return item;
1859	}
1860
1861	return NULL;
1862}
1863
1864
1865BRect
1866BMenu::Superbounds() const
1867{
1868	return fSuperbounds;
1869}
1870
1871
1872void
1873BMenu::CacheFontInfo()
1874{
1875	font_height fh;
1876	GetFontHeight(&fh);
1877	fAscent = fh.ascent;
1878	fDescent = fh.descent;
1879	fFontHeight = ceilf(fh.ascent + fh.descent + fh.leading);
1880}
1881
1882
1883void
1884BMenu::ItemMarked(BMenuItem *item)
1885{
1886	if (IsRadioMode()) {
1887		for (int32 i = 0; i < CountItems(); i++)
1888			if (ItemAt(i) != item)
1889				ItemAt(i)->SetMarked(false);
1890		InvalidateLayout();
1891	}
1892
1893	if (IsLabelFromMarked() && Superitem())
1894		Superitem()->SetLabel(item->Label());
1895}
1896
1897
1898void
1899BMenu::Install(BWindow *target)
1900{
1901	for (int32 i = 0; i < CountItems(); i++)
1902		ItemAt(i)->Install(target);
1903}
1904
1905
1906void
1907BMenu::Uninstall()
1908{
1909	for (int32 i = 0; i < CountItems(); i++)
1910		ItemAt(i)->Uninstall();
1911}
1912
1913
1914void
1915BMenu::_SelectItem(BMenuItem* menuItem, bool showSubmenu, bool selectFirstItem)
1916{
1917	// Avoid deselecting and then reselecting the same item
1918	// which would cause flickering
1919	if (menuItem != fSelected) {
1920		if (fSelected != NULL) {
1921			fSelected->Select(false);
1922			BMenu *subMenu = fSelected->Submenu();
1923			if (subMenu != NULL && subMenu->Window() != NULL)
1924				subMenu->_hide();
1925		}
1926
1927		fSelected = menuItem;
1928		if (fSelected != NULL)
1929			fSelected->Select(true);
1930	}
1931
1932	if (fSelected != NULL && showSubmenu) {
1933		BMenu *subMenu = fSelected->Submenu();
1934		if (subMenu != NULL && subMenu->Window() == NULL) {
1935			if (!subMenu->_show(selectFirstItem)) {
1936				// something went wrong, deselect the item
1937				fSelected->Select(false);
1938				fSelected = NULL;
1939			}
1940			//subMenu->Window()->Activate();
1941		}
1942	}
1943}
1944
1945
1946BMenuItem *
1947BMenu::CurrentSelection() const
1948{
1949	return fSelected;
1950}
1951
1952
1953bool
1954BMenu::SelectNextItem(BMenuItem *item, bool forward)
1955{
1956	BMenuItem *nextItem = NextItem(item, forward);
1957	if (nextItem == NULL)
1958		return false;
1959
1960	_SelectItem(nextItem);
1961	return true;
1962}
1963
1964
1965BMenuItem *
1966BMenu::NextItem(BMenuItem *item, bool forward) const
1967{
1968	if (item == NULL) {
1969		if (forward)
1970			return ItemAt(CountItems() - 1);
1971		else
1972			return ItemAt(0);
1973	}
1974
1975	int32 index = fItems.IndexOf(item);
1976	if (forward)
1977		index++;
1978	else
1979		index--;
1980
1981	if (index < 0 || index >= fItems.CountItems())
1982		return NULL;
1983
1984	return ItemAt(index);
1985}
1986
1987
1988bool
1989BMenu::IsItemVisible(BMenuItem *item) const
1990{
1991	BRect itemFrame = item->Frame();
1992	ConvertToScreen(&itemFrame);
1993
1994	BRect visibilityFrame = Window()->Frame() & BScreen(Window()).Frame();
1995
1996	return visibilityFrame.Intersects(itemFrame);
1997}
1998
1999
2000void
2001BMenu::SetIgnoreHidden(bool on)
2002{
2003	fIgnoreHidden = on;
2004}
2005
2006
2007void
2008BMenu::SetStickyMode(bool on)
2009{
2010	if (fStickyMode != on) {
2011		// TODO: Ugly hack, but it needs to be done right here in this method
2012		BMenuBar *menuBar = dynamic_cast<BMenuBar *>(this);
2013		if (on && menuBar != NULL && menuBar->LockLooper()) {
2014			// Steal the focus from the current focus view
2015			// (needed to handle keyboard navigation)
2016			menuBar->StealFocus();
2017			menuBar->UnlockLooper();
2018		}
2019
2020		fStickyMode = on;
2021	}
2022
2023	// If we are switching to sticky mode, propagate the status
2024	// back to the super menu
2025	if (on && fSuper != NULL)
2026		fSuper->SetStickyMode(on);
2027}
2028
2029
2030bool
2031BMenu::IsStickyMode() const
2032{
2033	return fStickyMode;
2034}
2035
2036
2037void
2038BMenu::CalcTriggers()
2039{
2040	BList triggersList;
2041
2042	// Gathers the existing triggers
2043	// TODO: Oh great, reinterpret_cast.
2044	for (int32 i = 0; i < CountItems(); i++) {
2045		char trigger = ItemAt(i)->Trigger();
2046		if (trigger != 0)
2047			triggersList.AddItem(reinterpret_cast<void *>((uint32)trigger));
2048	}
2049
2050	// Set triggers for items which don't have one yet
2051	for (int32 i = 0; i < CountItems(); i++) {
2052		BMenuItem *item = ItemAt(i);
2053		if (item->Trigger() == 0) {
2054			const char *newTrigger = ChooseTrigger(item->Label(), &triggersList);
2055			if (newTrigger != NULL)
2056				item->SetAutomaticTrigger(*newTrigger);
2057		}
2058	}
2059}
2060
2061
2062const char *
2063BMenu::ChooseTrigger(const char *title, BList *chars)
2064{
2065	ASSERT(chars != NULL);
2066
2067	if (title == NULL)
2068		return NULL;
2069
2070	char trigger;
2071	// TODO: Oh great, reinterpret_cast all around
2072	while ((trigger = title[0]) != '\0') {
2073		if (!chars->HasItem(reinterpret_cast<void *>((uint32)trigger)))	{
2074			chars->AddItem(reinterpret_cast<void *>((uint32)trigger));
2075			return title;
2076		}
2077
2078		title++;
2079	}
2080
2081	return NULL;
2082}
2083
2084
2085void
2086BMenu::UpdateWindowViewSize(bool upWind)
2087{
2088	BWindow *window = Window();
2089	if (window == NULL)
2090		return;
2091
2092	bool scroll;
2093	BRect frame = CalcFrame(ScreenLocation(), &scroll);
2094	ResizeTo(frame.Width(), frame.Height());
2095
2096	if (fItems.CountItems() > 0) {
2097		if (!scroll) {
2098			window->ResizeTo(Bounds().Width() + 2, Bounds().Height() + 2);
2099		} else {
2100			BScreen screen(window);
2101
2102			// If we need scrolling, resize the window to fit the screen and
2103			// attach scrollers to our cached MenuWindow.
2104			if (dynamic_cast<BMenuBar *>(Supermenu()) == NULL) {
2105				window->ResizeTo(Bounds().Width() + 2, screen.Frame().bottom - 10);
2106				frame.top = 0;
2107			}
2108			else {
2109			// Or, in case our parent was a BMenuBar enable scrolling with
2110			// normal size.
2111				window->ResizeTo(Bounds().Width() + 2, screen.Frame().bottom - frame.top);
2112			}
2113
2114			static_cast<BMenuWindow *>(window)->AttachScrollers();
2115		}
2116	} else {
2117		CacheFontInfo();
2118		window->ResizeTo(StringWidth(kEmptyMenuLabel) + fPad.left + fPad.right,
2119						fFontHeight + fPad.top + fPad.bottom);
2120	}
2121
2122	window->MoveTo(frame.LeftTop());
2123}
2124
2125
2126bool
2127BMenu::IsStickyPrefOn()
2128{
2129	return true;
2130}
2131
2132
2133void
2134BMenu::RedrawAfterSticky(BRect bounds)
2135{
2136}
2137
2138
2139bool
2140BMenu::OkToProceed(BMenuItem* item)
2141{
2142	bool proceed = true;
2143	BPoint where;
2144	ulong buttons;
2145	GetMouse(&where, &buttons, false);
2146	bool stickyMode = IsStickyMode();
2147	// Quit if user clicks the mouse button in sticky mode
2148	// or releases the mouse button in nonsticky mode
2149	// or moves the pointer over another item
2150	// TODO: I added the check for BMenuBar to solve a problem with Deskbar.
2151	// Beos seems to do something similar. This could also be a bug in Deskbar, though.
2152	if ((buttons != 0 && stickyMode)
2153		|| (dynamic_cast<BMenuBar *>(this) == NULL && (buttons == 0 && !stickyMode)
2154		|| HitTestItems(where) != item))
2155		proceed = false;
2156
2157
2158	return proceed;
2159}
2160
2161
2162void
2163BMenu::QuitTracking()
2164{
2165	_SelectItem(NULL);
2166	if (BMenuBar *menuBar = dynamic_cast<BMenuBar *>(this))
2167		menuBar->RestoreFocus();
2168
2169	fChosenItem = NULL;
2170	fState = MENU_STATE_CLOSED;
2171}
2172
2173
2174status_t
2175BMenu::ParseMsg(BMessage *msg, int32 *sindex, BMessage *spec,
2176						 int32 *form, const char **prop, BMenu **tmenu,
2177						 BMenuItem **titem, int32 *user_data,
2178						 BMessage *reply) const
2179{
2180	return B_ERROR;
2181}
2182
2183
2184status_t
2185BMenu::DoMenuMsg(BMenuItem **next, BMenu *menu, BMessage *message,
2186						  BMessage *r, BMessage *spec, int32 f) const
2187{
2188	return B_ERROR;
2189}
2190
2191
2192status_t
2193BMenu::DoMenuItemMsg(BMenuItem **next, BMenu *menu, BMessage *message,
2194							  BMessage *r, BMessage *spec, int32 f) const
2195{
2196	return B_ERROR;
2197}
2198
2199
2200status_t
2201BMenu::DoEnabledMsg(BMenuItem *ti, BMenu *tm, BMessage *m,
2202							 BMessage *r) const
2203{
2204	return B_ERROR;
2205}
2206
2207
2208status_t
2209BMenu::DoLabelMsg(BMenuItem *ti, BMenu *tm, BMessage *m,
2210						   BMessage *r) const
2211{
2212	return B_ERROR;
2213}
2214
2215
2216status_t
2217BMenu::DoMarkMsg(BMenuItem *ti, BMenu *tm, BMessage *m,
2218						  BMessage *r) const
2219{
2220	return B_ERROR;
2221}
2222
2223
2224status_t
2225BMenu::DoDeleteMsg(BMenuItem *ti, BMenu *tm, BMessage *m,
2226							BMessage *r) const
2227{
2228	return B_ERROR;
2229}
2230
2231
2232status_t
2233BMenu::DoCreateMsg(BMenuItem *ti, BMenu *tm, BMessage *m,
2234							BMessage *r, bool menu) const
2235{
2236	return B_ERROR;
2237}
2238
2239
2240// TODO: Maybe the following two methods would fit better into InterfaceDefs.cpp
2241// In R5, they do all the work client side, we let the app_server handle the details.
2242status_t
2243set_menu_info(menu_info *info)
2244{
2245	if (!info)
2246		return B_BAD_VALUE;
2247
2248	BPrivate::AppServerLink link;
2249	link.StartMessage(AS_SET_MENU_INFO);
2250	link.Attach<menu_info>(*info);
2251
2252	status_t status = B_ERROR;
2253	if (link.FlushWithReply(status) == B_OK && status == B_OK)
2254		BMenu::sMenuInfo = *info;
2255		// Update also the local copy, in case anyone relies on it
2256
2257	return status;
2258}
2259
2260
2261status_t
2262get_menu_info(menu_info *info)
2263{
2264	if (!info)
2265		return B_BAD_VALUE;
2266
2267	BPrivate::AppServerLink link;
2268	link.StartMessage(AS_GET_MENU_INFO);
2269
2270	status_t status = B_ERROR;
2271	if (link.FlushWithReply(status) == B_OK && status == B_OK)
2272		link.Read<menu_info>(info);
2273
2274	return status;
2275}
2276