Menu.cpp revision 68fb266820911697f0f32ac91fbca0eefbd6230a
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	int action;
1068	BMenuItem *menuItem = _track(&action);
1069
1070	SetStickyMode(false);
1071	fExtraRect = NULL;
1072
1073	return menuItem;
1074}
1075
1076
1077bool
1078BMenu::AddDynamicItem(add_state s)
1079{
1080	// Implemented in subclasses
1081	return false;
1082}
1083
1084
1085void
1086BMenu::DrawBackground(BRect update)
1087{
1088	rgb_color oldColor = HighColor();
1089	SetHighColor(sMenuInfo.background_color);
1090	FillRect(Bounds() & update, B_SOLID_HIGH);
1091	SetHighColor(oldColor);
1092}
1093
1094
1095void
1096BMenu::SetTrackingHook(menu_tracking_hook func, void *state)
1097{
1098	delete fExtraMenuData;
1099	fExtraMenuData = new (nothrow) _ExtraMenuData_(func, state);
1100}
1101
1102
1103void BMenu::_ReservedMenu3() {}
1104void BMenu::_ReservedMenu4() {}
1105void BMenu::_ReservedMenu5() {}
1106void BMenu::_ReservedMenu6() {}
1107
1108
1109BMenu &
1110BMenu::operator=(const BMenu &)
1111{
1112	return *this;
1113}
1114
1115
1116void
1117BMenu::InitData(BMessage *data)
1118{
1119	// TODO: Get _color, _fname, _fflt from the message, if present
1120	BFont font;
1121	font.SetFamilyAndStyle(sMenuInfo.f_family, sMenuInfo.f_style);
1122	font.SetSize(sMenuInfo.font_size);
1123	SetFont(&font, B_FONT_FAMILY_AND_STYLE | B_FONT_SIZE);
1124
1125	SetLowColor(sMenuInfo.background_color);
1126	SetViewColor(sMenuInfo.background_color);
1127
1128	if (data != NULL) {
1129		data->FindInt32("_layout", (int32 *)&fLayout);
1130		data->FindBool("_rsize_to_fit", &fResizeToFit);
1131		bool disabled;
1132		if (data->FindBool("_disable", &disabled) == B_OK)
1133			fEnabled = !disabled;
1134		data->FindBool("_radio", &fRadioMode);
1135
1136		bool disableTrigger = false;
1137		data->FindBool("_trig_disabled", &disableTrigger);
1138		fTriggerEnabled = !disableTrigger;
1139
1140		data->FindBool("_dyn_label", &fDynamicName);
1141		data->FindFloat("_maxwidth", &fMaxContentWidth);
1142
1143		BMessage msg;
1144        	for (int32 i = 0; data->FindMessage("_items", i, &msg) == B_OK; i++) {
1145			BArchivable *object = instantiate_object(&msg);
1146			if (BMenuItem *item = dynamic_cast<BMenuItem *>(object)) {
1147				BRect bounds;
1148				if ((fLayout == B_ITEMS_IN_MATRIX)
1149					&& (data->FindRect("_i_frames", i, &bounds) == B_OK))
1150						AddItem(item, bounds);
1151				else
1152					AddItem(item);
1153			}
1154		}
1155	}
1156}
1157
1158
1159bool
1160BMenu::_show(bool selectFirstItem)
1161{
1162	// See if the supermenu has a cached menuwindow,
1163	// and use that one if possible.
1164	BMenuWindow *window = NULL;
1165	bool ourWindow = false;
1166	if (fSuper != NULL) {
1167		fSuperbounds = fSuper->ConvertToScreen(fSuper->Bounds());
1168		window = fSuper->MenuWindow();
1169	}
1170
1171	// Otherwise, create a new one
1172	// This happens for "stand alone" BPopUpMenus
1173	// (i.e. not within a BMenuField)
1174	if (window == NULL) {
1175		// Menu windows get the BMenu's handler name
1176		window = new (nothrow) BMenuWindow(Name());
1177		ourWindow = true;
1178	}
1179
1180	if (window == NULL)
1181		return false;
1182
1183	if (window->Lock()) {
1184		fAttachAborted = false;
1185		window->AttachMenu(this);
1186
1187		// Menu didn't have the time to add its items: aborting...
1188		if (fAttachAborted) {
1189			window->DetachMenu();
1190			// TODO: Probably not needed, we can just let _hide() quit the window
1191			if (ourWindow)
1192				window->Quit();
1193			else
1194				window->Unlock();
1195			return false;
1196		}
1197
1198		// Move the BMenu to 1, 1, if it's attached to a BMenuWindow,
1199		// (that means it's a BMenu, BMenuBars are attached to regular BWindows).
1200		// This is needed to be able to draw the frame around the BMenu.
1201		if (dynamic_cast<BMenuWindow *>(window) != NULL)
1202			MoveTo(1, 1);
1203
1204		UpdateWindowViewSize();
1205		window->Show();
1206
1207		if (selectFirstItem)
1208			_SelectItem(ItemAt(0));
1209
1210		window->Unlock();
1211	}
1212
1213	return true;
1214}
1215
1216
1217void
1218BMenu::_hide()
1219{
1220	BMenuWindow *window = static_cast<BMenuWindow *>(Window());
1221	if (window == NULL || !window->Lock())
1222		return;
1223
1224	if (fSelected != NULL)
1225		_SelectItem(NULL);
1226
1227	window->Hide();
1228	window->DetachMenu();
1229		// we don't want to be deleted when the window is removed
1230
1231	// Delete the menu window used by our submenus
1232	DeleteMenuWindow();
1233
1234	if (fSuper != NULL)
1235		window->Unlock();
1236	else {
1237		// it's our window, quit it
1238		window->Quit();
1239	}
1240}
1241
1242
1243const bigtime_t kHysteresis = 200000; // TODO: Test and reduce if needed.
1244
1245
1246BMenuItem *
1247BMenu::_track(int *action, long start)
1248{
1249	// TODO: cleanup
1250	BMenuItem *item = NULL;
1251	bigtime_t openTime = system_time();
1252	bigtime_t closeTime = 0;
1253
1254	fState = MENU_STATE_TRACKING;
1255	if (fSuper != NULL)
1256		fSuper->fState = MENU_STATE_TRACKING_SUBMENU;
1257
1258	while (true) {
1259		if (CustomTrackingWantsToQuit())
1260			break;
1261
1262		bool locked = LockLooper();
1263		if (!locked)
1264			break;
1265
1266		bigtime_t snoozeAmount = 50000;
1267		BPoint location;
1268		ulong buttons;
1269		GetMouse(&location, &buttons, true);
1270
1271		BMenuWindow *window = static_cast<BMenuWindow *>(Window());
1272
1273		BPoint screenLocation = ConvertToScreen(location);
1274		if (window->CheckForScrolling(screenLocation)) {
1275			item = NULL;
1276		} else {
1277			item = HitTestItems(location, B_ORIGIN);
1278			if (item != NULL)
1279				_UpdateStateOpenSelect(item, openTime, closeTime);
1280		}
1281
1282		// Track the submenu
1283		if (OverSubmenu(fSelected, screenLocation)) {
1284			UnlockLooper();
1285			locked = false;
1286			int submenuAction = MENU_STATE_TRACKING;
1287			BMenu *submenu = fSelected->Submenu();
1288			bool wasSticky = IsStickyMode();
1289			if (wasSticky)
1290				submenu->SetStickyMode(true);
1291			BMenuItem *submenuItem = submenu->_track(&submenuAction);
1292
1293			// check if the user started holding down a mouse button in a submenu
1294			if (wasSticky && !IsStickyMode()) {
1295				buttons = 1;
1296					// buttons must have been pressed in the meantime
1297			}
1298
1299			if (submenuAction == MENU_STATE_CLOSED) {
1300				item = submenuItem;
1301				fState = submenuAction;
1302				break;
1303			}
1304
1305			locked = LockLooper();
1306			if (!locked)
1307				break;
1308
1309		} else if (item == NULL) {
1310			if (OverSuper(screenLocation)) {
1311				fState = MENU_STATE_TRACKING;
1312				UnlockLooper();
1313				break;
1314			}
1315
1316			if (!OverSubmenu(fSelected, screenLocation)
1317				&& system_time() > closeTime + kHysteresis
1318				&& fState != MENU_STATE_TRACKING_SUBMENU) {
1319				_SelectItem(NULL);
1320				fState = MENU_STATE_TRACKING;
1321			}
1322
1323			if (fSuper != NULL) {
1324				// Give supermenu the chance to continue tracking
1325				*action = fState;
1326				if (locked)
1327					UnlockLooper();
1328
1329				return NULL;
1330			}
1331		}
1332
1333		if (locked)
1334			UnlockLooper();
1335
1336		_UpdateStateClose(item, location, buttons);
1337
1338		if (fState == MENU_STATE_CLOSED)
1339			break;
1340
1341		snooze(snoozeAmount);
1342	}
1343
1344	if (action != NULL)
1345		*action = fState;
1346
1347	if (fSelected != NULL && LockLooper()) {
1348		_SelectItem(NULL);
1349		UnlockLooper();
1350	}
1351
1352	if (IsStickyMode())
1353		SetStickyMode(false);
1354
1355	// delete the menu window recycled for all the child menus
1356	DeleteMenuWindow();
1357
1358	return item;
1359}
1360
1361
1362void
1363BMenu::_UpdateStateOpenSelect(BMenuItem *item, bigtime_t &openTime, bigtime_t &closeTime)
1364{
1365	if (fState == MENU_STATE_CLOSED)
1366		return;
1367
1368	if (item != fSelected && system_time() > closeTime + kHysteresis) {
1369		_SelectItem(item, false);
1370		openTime = system_time();
1371	} else if (system_time() > kHysteresis + openTime && item->Submenu() != NULL
1372		&& item->Submenu()->Window() == NULL) {
1373		// Open the submenu if it's not opened yet, but only if
1374		// the mouse pointer stayed over there for some time
1375		// (hysteresis)
1376		_SelectItem(item);
1377		closeTime = system_time();
1378	}
1379	if (fState != MENU_STATE_TRACKING)
1380		fState = MENU_STATE_TRACKING;
1381}
1382
1383
1384void
1385BMenu::_UpdateStateClose(BMenuItem *item, const BPoint &where, const uint32 &buttons)
1386{
1387	if (fState == MENU_STATE_CLOSED)
1388		return;
1389
1390	if (buttons != 0 && IsStickyMode()) {
1391		if (item == NULL)
1392			fState = MENU_STATE_CLOSED;
1393		else {
1394			BMenu *supermenu = Supermenu();
1395			for(; supermenu; supermenu = supermenu->Supermenu())
1396				supermenu->SetStickyMode(false);
1397			SetStickyMode(false);
1398		}
1399	} else if (buttons == 0 && !IsStickyMode()) {
1400		if (fExtraRect != NULL && fExtraRect->Contains(where)) {
1401			SetStickyMode(true);
1402			fExtraRect = NULL;
1403				// This code should be executed only once
1404		} else
1405			fState = MENU_STATE_CLOSED;
1406	}
1407}
1408
1409
1410bool
1411BMenu::_AddItem(BMenuItem *item, int32 index)
1412{
1413	ASSERT(item != NULL);
1414	if (index < 0 || index > fItems.CountItems())
1415		return false;
1416
1417	if (!fItems.AddItem(item, index))
1418		return false;
1419
1420	// install the item on the supermenu's window
1421	// or onto our window, if we are a root menu
1422	BWindow* window = NULL;
1423	if (Superitem() != NULL)
1424		window = Superitem()->fWindow;
1425	else
1426		window = Window();
1427	if (window != NULL)
1428		item->Install(window);
1429
1430	item->SetSuper(this);
1431
1432	return true;
1433}
1434
1435
1436bool
1437BMenu::RemoveItems(int32 index, int32 count, BMenuItem *item, bool deleteItems)
1438{
1439	bool success = false;
1440	bool invalidateLayout = false;
1441
1442	bool locked = LockLooper();
1443	BWindow *window = Window();
1444
1445	// The plan is simple: If we're given a BMenuItem directly, we use it
1446	// and ignore index and count. Otherwise, we use them instead.
1447	if (item != NULL) {
1448		if (fItems.RemoveItem(item)) {
1449			if (item == fSelected && window != NULL)
1450				_SelectItem(NULL);
1451			item->Uninstall();
1452			item->SetSuper(NULL);
1453			if (deleteItems)
1454				delete item;
1455			success = invalidateLayout = true;
1456		}
1457	} else {
1458		// We iterate backwards because it's simpler
1459		int32 i = min_c(index + count - 1, fItems.CountItems() - 1);
1460		// NOTE: the range check for "index" is done after
1461		// calculating the last index to be removed, so
1462		// that the range is not "shifted" unintentionally
1463		index = max_c(0, index);
1464		for (; i >= index; i--) {
1465			item = static_cast<BMenuItem*>(fItems.ItemAt(i));
1466			if (item != NULL) {
1467				if (fItems.RemoveItem(item)) {
1468					if (item == fSelected && window != NULL)
1469						_SelectItem(NULL);
1470					item->Uninstall();
1471					item->SetSuper(NULL);
1472					if (deleteItems)
1473						delete item;
1474					success = true;
1475					invalidateLayout = true;
1476				} else {
1477					// operation not entirely successful
1478					success = false;
1479					break;
1480				}
1481			}
1482		}
1483	}
1484
1485	if (invalidateLayout && locked && window != NULL) {
1486		LayoutItems(0);
1487		//UpdateWindowViewSize();
1488		Invalidate();
1489	}
1490
1491	if (locked)
1492		UnlockLooper();
1493
1494	return success;
1495}
1496
1497
1498bool
1499BMenu::RelayoutIfNeeded()
1500{
1501	if (!fUseCachedMenuLayout) {
1502		fUseCachedMenuLayout = true;
1503		CacheFontInfo();
1504		LayoutItems(0);
1505		return true;
1506	}
1507	return false;
1508}
1509
1510
1511void
1512BMenu::LayoutItems(int32 index)
1513{
1514	CalcTriggers();
1515
1516	float width, height;
1517	ComputeLayout(index, fResizeToFit, true, &width, &height);
1518
1519	ResizeTo(width, height);
1520}
1521
1522
1523void
1524BMenu::ComputeLayout(int32 index, bool bestFit, bool moveItems,
1525	float* _width, float* _height)
1526{
1527	// TODO: Take "bestFit", "moveItems", "index" into account,
1528	// Recalculate only the needed items,
1529	// not the whole layout every time
1530
1531	BRect frame(0, 0, 0, 0);
1532	float iWidth, iHeight;
1533	BMenuItem *item = NULL;
1534
1535	BFont font;
1536	GetFont(&font);
1537	switch (fLayout) {
1538		case B_ITEMS_IN_COLUMN:
1539		{
1540			for (int32 i = 0; i < fItems.CountItems(); i++) {
1541				item = ItemAt(i);
1542				if (item != NULL) {
1543					item->GetContentSize(&iWidth, &iHeight);
1544
1545					if (item->fModifiers && item->fShortcutChar)
1546						iWidth += 2 * font.Size();
1547					if (item->fSubmenu != NULL)
1548						iWidth += 2 * font.Size();
1549
1550					item->fBounds.left = 0.0f;
1551					item->fBounds.top = frame.bottom;
1552					item->fBounds.bottom = item->fBounds.top + iHeight + fPad.top + fPad.bottom;
1553
1554					frame.right = max_c(frame.right, iWidth + fPad.left + fPad.right);
1555					frame.bottom = item->fBounds.bottom + 1.0f;
1556				}
1557			}
1558			if (fMaxContentWidth > 0)
1559				frame.right = min_c(frame.right, fMaxContentWidth);
1560
1561			if (moveItems) {
1562				for (int32 i = 0; i < fItems.CountItems(); i++)
1563					ItemAt(i)->fBounds.right = frame.right;
1564			}
1565			frame.right = ceilf(frame.right);
1566			frame.bottom--;
1567			break;
1568		}
1569
1570		case B_ITEMS_IN_ROW:
1571		{
1572			font_height fh;
1573			GetFontHeight(&fh);
1574			frame = BRect(0.0f, 0.0f, 0.0f,	ceilf(fh.ascent + fh.descent + fPad.top + fPad.bottom));
1575
1576			for (int32 i = 0; i < fItems.CountItems(); i++) {
1577				item = ItemAt(i);
1578				if (item != NULL) {
1579					item->GetContentSize(&iWidth, &iHeight);
1580
1581					item->fBounds.left = frame.right;
1582					item->fBounds.top = 0.0f;
1583					item->fBounds.right = item->fBounds.left + iWidth + fPad.left + fPad.right;
1584
1585					frame.right = item->Frame().right + 1.0f;
1586					frame.bottom = max_c(frame.bottom, iHeight + fPad.top + fPad.bottom);
1587				}
1588			}
1589
1590			if (moveItems) {
1591				for (int32 i = 0; i < fItems.CountItems(); i++)
1592					ItemAt(i)->fBounds.bottom = frame.bottom;
1593			}
1594
1595			if (bestFit)
1596				frame.right = ceilf(frame.right);
1597			else
1598				frame.right = Bounds().right;
1599			break;
1600		}
1601
1602		case B_ITEMS_IN_MATRIX:
1603		{
1604			for (int32 i = 0; i < CountItems(); i++) {
1605				item = ItemAt(i);
1606				if (item != NULL) {
1607					frame.left = min_c(frame.left, item->Frame().left);
1608					frame.right = max_c(frame.right, item->Frame().right);
1609					frame.top = min_c(frame.top, item->Frame().top);
1610					frame.bottom = max_c(frame.bottom, item->Frame().bottom);
1611				}
1612			}
1613			break;
1614		}
1615
1616		default:
1617			break;
1618	}
1619
1620	if (_width) {
1621		// change width depending on resize mode
1622		if ((ResizingMode() & B_FOLLOW_LEFT_RIGHT) == B_FOLLOW_LEFT_RIGHT) {
1623			if (Parent())
1624				*_width = Parent()->Frame().Width() + 1;
1625			else if (Window())
1626				*_width = Window()->Frame().Width() + 1;
1627			else
1628				*_width = Bounds().Width();
1629		} else
1630			*_width = frame.Width();
1631	}
1632
1633	if (_height)
1634		*_height = frame.Height();
1635
1636	if (moveItems)
1637		fUseCachedMenuLayout = true;
1638}
1639
1640
1641BRect
1642BMenu::Bump(BRect current, BPoint extent, int32 index) const
1643{
1644	// ToDo: what's this?
1645	return BRect();
1646}
1647
1648
1649BPoint
1650BMenu::ItemLocInRect(BRect frame) const
1651{
1652	// ToDo: what's this?
1653	return BPoint();
1654}
1655
1656
1657BPoint
1658BMenu::ScreenLocation()
1659{
1660	BMenu *superMenu = Supermenu();
1661	BMenuItem *superItem = Superitem();
1662
1663	if (superMenu == NULL || superItem == NULL) {
1664		debugger("BMenu can't determine where to draw."
1665			"Override BMenu::ScreenLocation() to determine location.");
1666	}
1667
1668	BPoint point;
1669	if (superMenu->Layout() == B_ITEMS_IN_COLUMN)
1670		point = superItem->Frame().RightTop() + BPoint(1.0f, 1.0f);
1671	else
1672		point = superItem->Frame().LeftBottom() + BPoint(1.0f, 1.0f);
1673
1674	superMenu->ConvertToScreen(&point);
1675
1676	return point;
1677}
1678
1679
1680BRect
1681BMenu::CalcFrame(BPoint where, bool *scrollOn)
1682{
1683	// TODO: Improve me
1684	BRect bounds = Bounds();
1685	BRect frame = bounds.OffsetToCopy(where);
1686
1687	BScreen screen(Window());
1688	BRect screenFrame = screen.Frame();
1689
1690	BMenu *superMenu = Supermenu();
1691	BMenuItem *superItem = Superitem();
1692
1693	if (scrollOn != NULL) {
1694		// basically, if this returns false, it means
1695		// that the menu frame won't fit completely inside the screen
1696		*scrollOn = !screenFrame.Contains(bounds);
1697	}
1698
1699	// TODO: Horrible hack:
1700	// When added to a BMenuField, a BPopUpMenu is the child of
1701	// a _BMCMenuBar_ to "fake" the menu hierarchy
1702	if (superMenu == NULL || superItem == NULL
1703		|| dynamic_cast<_BMCMenuBar_ *>(superMenu) != NULL) {
1704		// just move the window on screen
1705
1706		if (frame.bottom > screenFrame.bottom)
1707			frame.OffsetBy(0, screenFrame.bottom - frame.bottom);
1708		else if (frame.top < screenFrame.top)
1709			frame.OffsetBy(0, -frame.top);
1710
1711		if (frame.right > screenFrame.right)
1712			frame.OffsetBy(screenFrame.right - frame.right, 0);
1713		else if (frame.left < screenFrame.left)
1714			frame.OffsetBy(-frame.left, 0);
1715
1716		return frame;
1717	}
1718
1719	if (superMenu->Layout() == B_ITEMS_IN_COLUMN) {
1720		if (frame.right > screenFrame.right)
1721			frame.OffsetBy(-superItem->Frame().Width() - frame.Width() - 2, 0);
1722
1723		if (frame.left < 0)
1724			frame.OffsetBy(-frame.left + 6, 0);
1725
1726		if (frame.bottom > screenFrame.bottom)
1727			frame.OffsetBy(0, screenFrame.bottom - frame.bottom);
1728	} else {
1729		if (frame.bottom > screenFrame.bottom) {
1730			if (scrollOn != NULL && superMenu != NULL &&
1731				dynamic_cast<BMenuBar *>(superMenu) != NULL &&
1732				frame.top < (screenFrame.bottom - 80)) {
1733				*scrollOn = true;
1734			} else {
1735				frame.OffsetBy(0, -superItem->Frame().Height() - frame.Height() - 3);
1736			}
1737		}
1738
1739		if (frame.right > screenFrame.right)
1740			frame.OffsetBy(screenFrame.right - frame.right, 0);
1741	}
1742
1743	return frame;
1744}
1745
1746
1747bool
1748BMenu::ScrollMenu(BRect bounds, BPoint loc, bool *fast)
1749{
1750	return false;
1751}
1752
1753
1754void
1755BMenu::ScrollIntoView(BMenuItem *item)
1756{
1757}
1758
1759
1760void
1761BMenu::DrawItems(BRect updateRect)
1762{
1763	int32 itemCount = fItems.CountItems();
1764	for (int32 i = 0; i < itemCount; i++) {
1765		BMenuItem *item = ItemAt(i);
1766		if (item->Frame().Intersects(updateRect))
1767			item->Draw();
1768	}
1769}
1770
1771
1772int
1773BMenu::State(BMenuItem **item) const
1774{
1775	if (fState == MENU_STATE_TRACKING || fState == MENU_STATE_CLOSED)
1776		return fState;
1777
1778	if (fSelected != NULL && fSelected->Submenu() != NULL)
1779		return fSelected->Submenu()->State(item);
1780
1781	return fState;
1782}
1783
1784
1785void
1786BMenu::InvokeItem(BMenuItem *item, bool now)
1787{
1788	if (!item->IsEnabled())
1789		return;
1790
1791	// Do the "selected" animation
1792	if (!item->Submenu() && LockLooper()) {
1793		snooze(50000);
1794		item->Select(true);
1795		Sync();
1796		snooze(50000);
1797		item->Select(false);
1798		Sync();
1799		snooze(50000);
1800		item->Select(true);
1801		Sync();
1802		snooze(50000);
1803		item->Select(false);
1804		Sync();
1805		UnlockLooper();
1806	}
1807
1808	item->Invoke();
1809}
1810
1811
1812bool
1813BMenu::OverSuper(BPoint location)
1814{
1815	if (!Supermenu())
1816		return false;
1817
1818	return fSuperbounds.Contains(location);
1819}
1820
1821
1822bool
1823BMenu::OverSubmenu(BMenuItem *item, BPoint loc)
1824{
1825	if (item == NULL)
1826		return false;
1827
1828	BMenu *subMenu = item->Submenu();
1829	if (subMenu == NULL || subMenu->Window() == NULL)
1830		return false;
1831
1832	// we assume that loc is in screen coords
1833	if (subMenu->Window()->Frame().Contains(loc))
1834		return true;
1835
1836	return subMenu->OverSubmenu(subMenu->fSelected, loc);
1837}
1838
1839
1840BMenuWindow *
1841BMenu::MenuWindow()
1842{
1843	if (fCachedMenuWindow == NULL) {
1844		char windowName[64];
1845		snprintf(windowName, 64, "%s cached menu", Name());
1846		fCachedMenuWindow = new (nothrow) BMenuWindow(windowName);
1847	}
1848
1849	return fCachedMenuWindow;
1850}
1851
1852
1853void
1854BMenu::DeleteMenuWindow()
1855{
1856	if (fCachedMenuWindow != NULL) {
1857		fCachedMenuWindow->Lock();
1858		fCachedMenuWindow->Quit();
1859		fCachedMenuWindow = NULL;
1860	}
1861}
1862
1863
1864BMenuItem *
1865BMenu::HitTestItems(BPoint where, BPoint slop) const
1866{
1867	// TODO: Take "slop" into account ?
1868
1869	// if the point doesn't lie within the menu's
1870	// bounds, bail out immediately
1871	if (!Bounds().Contains(where))
1872		return NULL;
1873
1874	int32 itemCount = CountItems();
1875	for (int32 i = 0; i < itemCount; i++) {
1876		BMenuItem *item = ItemAt(i);
1877		if (item->Frame().Contains(where))
1878			return item;
1879	}
1880
1881	return NULL;
1882}
1883
1884
1885BRect
1886BMenu::Superbounds() const
1887{
1888	return fSuperbounds;
1889}
1890
1891
1892void
1893BMenu::CacheFontInfo()
1894{
1895	font_height fh;
1896	GetFontHeight(&fh);
1897	fAscent = fh.ascent;
1898	fDescent = fh.descent;
1899	fFontHeight = ceilf(fh.ascent + fh.descent + fh.leading);
1900}
1901
1902
1903void
1904BMenu::ItemMarked(BMenuItem *item)
1905{
1906	if (IsRadioMode()) {
1907		for (int32 i = 0; i < CountItems(); i++)
1908			if (ItemAt(i) != item)
1909				ItemAt(i)->SetMarked(false);
1910		InvalidateLayout();
1911	}
1912
1913	if (IsLabelFromMarked() && Superitem())
1914		Superitem()->SetLabel(item->Label());
1915}
1916
1917
1918void
1919BMenu::Install(BWindow *target)
1920{
1921	for (int32 i = 0; i < CountItems(); i++)
1922		ItemAt(i)->Install(target);
1923}
1924
1925
1926void
1927BMenu::Uninstall()
1928{
1929	for (int32 i = 0; i < CountItems(); i++)
1930		ItemAt(i)->Uninstall();
1931}
1932
1933
1934void
1935BMenu::_SelectItem(BMenuItem* menuItem, bool showSubmenu, bool selectFirstItem)
1936{
1937	// Avoid deselecting and then reselecting the same item
1938	// which would cause flickering
1939	if (menuItem != fSelected) {
1940		if (fSelected != NULL) {
1941			fSelected->Select(false);
1942			BMenu *subMenu = fSelected->Submenu();
1943			if (subMenu != NULL && subMenu->Window() != NULL)
1944				subMenu->_hide();
1945		}
1946
1947		fSelected = menuItem;
1948		if (fSelected != NULL)
1949			fSelected->Select(true);
1950	}
1951
1952	if (fSelected != NULL && showSubmenu) {
1953		BMenu *subMenu = fSelected->Submenu();
1954		if (subMenu != NULL && subMenu->Window() == NULL) {
1955			if (!subMenu->_show(selectFirstItem)) {
1956				// something went wrong, deselect the item
1957				fSelected->Select(false);
1958				fSelected = NULL;
1959			}
1960		}
1961	}
1962}
1963
1964
1965BMenuItem *
1966BMenu::CurrentSelection() const
1967{
1968	return fSelected;
1969}
1970
1971
1972bool
1973BMenu::SelectNextItem(BMenuItem *item, bool forward)
1974{
1975	BMenuItem *nextItem = NextItem(item, forward);
1976	if (nextItem == NULL)
1977		return false;
1978
1979	_SelectItem(nextItem);
1980	return true;
1981}
1982
1983
1984BMenuItem *
1985BMenu::NextItem(BMenuItem *item, bool forward) const
1986{
1987	if (item == NULL) {
1988		if (forward)
1989			return ItemAt(CountItems() - 1);
1990		else
1991			return ItemAt(0);
1992	}
1993
1994	int32 index = fItems.IndexOf(item);
1995	if (forward)
1996		index++;
1997	else
1998		index--;
1999
2000	if (index < 0 || index >= fItems.CountItems())
2001		return NULL;
2002
2003	return ItemAt(index);
2004}
2005
2006
2007bool
2008BMenu::IsItemVisible(BMenuItem *item) const
2009{
2010	BRect itemFrame = item->Frame();
2011	ConvertToScreen(&itemFrame);
2012
2013	BRect visibilityFrame = Window()->Frame() & BScreen(Window()).Frame();
2014
2015	return visibilityFrame.Intersects(itemFrame);
2016}
2017
2018
2019void
2020BMenu::SetIgnoreHidden(bool on)
2021{
2022	fIgnoreHidden = on;
2023}
2024
2025
2026void
2027BMenu::SetStickyMode(bool on)
2028{
2029	if (fStickyMode != on) {
2030		// TODO: Ugly hack, but it needs to be done right here in this method
2031		BMenuBar *menuBar = dynamic_cast<BMenuBar *>(this);
2032		if (on && menuBar != NULL && menuBar->LockLooper()) {
2033			// Steal the focus from the current focus view
2034			// (needed to handle keyboard navigation)
2035			menuBar->StealFocus();
2036			menuBar->UnlockLooper();
2037		}
2038
2039		fStickyMode = on;
2040	}
2041
2042	// If we are switching to sticky mode, propagate the status
2043	// back to the super menu
2044	if (on && fSuper != NULL)
2045		fSuper->SetStickyMode(on);
2046}
2047
2048
2049bool
2050BMenu::IsStickyMode() const
2051{
2052	return fStickyMode;
2053}
2054
2055
2056void
2057BMenu::CalcTriggers()
2058{
2059	BList triggersList;
2060
2061	// Gathers the existing triggers
2062	// TODO: Oh great, reinterpret_cast.
2063	for (int32 i = 0; i < CountItems(); i++) {
2064		char trigger = ItemAt(i)->Trigger();
2065		if (trigger != 0)
2066			triggersList.AddItem(reinterpret_cast<void *>((uint32)trigger));
2067	}
2068
2069	// Set triggers for items which don't have one yet
2070	for (int32 i = 0; i < CountItems(); i++) {
2071		BMenuItem *item = ItemAt(i);
2072		if (item->Trigger() == 0) {
2073			const char *newTrigger = ChooseTrigger(item->Label(), &triggersList);
2074			if (newTrigger != NULL)
2075				item->SetAutomaticTrigger(*newTrigger);
2076		}
2077	}
2078}
2079
2080
2081const char *
2082BMenu::ChooseTrigger(const char *title, BList *chars)
2083{
2084	ASSERT(chars != NULL);
2085
2086	if (title == NULL)
2087		return NULL;
2088
2089	char trigger;
2090	// TODO: Oh great, reinterpret_cast all around
2091	while ((trigger = title[0]) != '\0') {
2092		if (!chars->HasItem(reinterpret_cast<void *>((uint32)trigger)))	{
2093			chars->AddItem(reinterpret_cast<void *>((uint32)trigger));
2094			return title;
2095		}
2096
2097		title++;
2098	}
2099
2100	return NULL;
2101}
2102
2103
2104void
2105BMenu::UpdateWindowViewSize(bool upWind)
2106{
2107	BWindow *window = Window();
2108	if (window == NULL)
2109		return;
2110
2111	bool scroll;
2112	BRect frame = CalcFrame(ScreenLocation(), &scroll);
2113	ResizeTo(frame.Width(), frame.Height());
2114
2115	if (fItems.CountItems() > 0) {
2116		if (!scroll) {
2117			window->ResizeTo(Bounds().Width() + 2, Bounds().Height() + 2);
2118		} else {
2119			BScreen screen(window);
2120
2121			// If we need scrolling, resize the window to fit the screen and
2122			// attach scrollers to our cached MenuWindow.
2123			if (dynamic_cast<BMenuBar *>(Supermenu()) == NULL) {
2124				window->ResizeTo(Bounds().Width() + 2, screen.Frame().bottom);
2125				frame.top = 0;
2126			} else {
2127				// Or, in case our parent was a BMenuBar enable scrolling with
2128				// normal size.
2129				window->ResizeTo(Bounds().Width() + 2, screen.Frame().bottom - frame.top);
2130			}
2131
2132			static_cast<BMenuWindow *>(window)->AttachScrollers();
2133		}
2134	} else {
2135		CacheFontInfo();
2136		window->ResizeTo(StringWidth(kEmptyMenuLabel) + fPad.left + fPad.right,
2137						fFontHeight + fPad.top + fPad.bottom);
2138	}
2139
2140	window->MoveTo(frame.LeftTop());
2141}
2142
2143
2144bool
2145BMenu::IsStickyPrefOn()
2146{
2147	return true;
2148}
2149
2150
2151void
2152BMenu::RedrawAfterSticky(BRect bounds)
2153{
2154}
2155
2156
2157bool
2158BMenu::OkToProceed(BMenuItem* item)
2159{
2160	bool proceed = true;
2161	BPoint where;
2162	ulong buttons;
2163	GetMouse(&where, &buttons, false);
2164	bool stickyMode = IsStickyMode();
2165	// Quit if user clicks the mouse button in sticky mode
2166	// or releases the mouse button in nonsticky mode
2167	// or moves the pointer over another item
2168	// TODO: I added the check for BMenuBar to solve a problem with Deskbar.
2169	// Beos seems to do something similar. This could also be a bug in Deskbar, though.
2170	if ((buttons != 0 && stickyMode)
2171		|| (dynamic_cast<BMenuBar *>(this) == NULL && (buttons == 0 && !stickyMode)
2172		|| HitTestItems(where) != item))
2173		proceed = false;
2174
2175
2176	return proceed;
2177}
2178
2179
2180bool
2181BMenu::CustomTrackingWantsToQuit()
2182{
2183	if (fExtraMenuData != NULL && fExtraMenuData->trackingHook != NULL
2184		&& fExtraMenuData->trackingState != NULL) {
2185		return fExtraMenuData->trackingHook(this, fExtraMenuData->trackingState);
2186	}
2187
2188	return false;
2189}
2190
2191
2192void
2193BMenu::QuitTracking()
2194{
2195	_SelectItem(NULL);
2196	if (BMenuBar *menuBar = dynamic_cast<BMenuBar *>(this))
2197		menuBar->RestoreFocus();
2198
2199	fChosenItem = NULL;
2200	fState = MENU_STATE_CLOSED;
2201}
2202
2203
2204status_t
2205BMenu::ParseMsg(BMessage *msg, int32 *sindex, BMessage *spec,
2206						 int32 *form, const char **prop, BMenu **tmenu,
2207						 BMenuItem **titem, int32 *user_data,
2208						 BMessage *reply) const
2209{
2210	return B_ERROR;
2211}
2212
2213
2214status_t
2215BMenu::DoMenuMsg(BMenuItem **next, BMenu *menu, BMessage *message,
2216						  BMessage *r, BMessage *spec, int32 f) const
2217{
2218	return B_ERROR;
2219}
2220
2221
2222status_t
2223BMenu::DoMenuItemMsg(BMenuItem **next, BMenu *menu, BMessage *message,
2224							  BMessage *r, BMessage *spec, int32 f) const
2225{
2226	return B_ERROR;
2227}
2228
2229
2230status_t
2231BMenu::DoEnabledMsg(BMenuItem *ti, BMenu *tm, BMessage *m,
2232							 BMessage *r) const
2233{
2234	return B_ERROR;
2235}
2236
2237
2238status_t
2239BMenu::DoLabelMsg(BMenuItem *ti, BMenu *tm, BMessage *m,
2240						   BMessage *r) const
2241{
2242	return B_ERROR;
2243}
2244
2245
2246status_t
2247BMenu::DoMarkMsg(BMenuItem *ti, BMenu *tm, BMessage *m,
2248						  BMessage *r) const
2249{
2250	return B_ERROR;
2251}
2252
2253
2254status_t
2255BMenu::DoDeleteMsg(BMenuItem *ti, BMenu *tm, BMessage *m,
2256							BMessage *r) const
2257{
2258	return B_ERROR;
2259}
2260
2261
2262status_t
2263BMenu::DoCreateMsg(BMenuItem *ti, BMenu *tm, BMessage *m,
2264							BMessage *r, bool menu) const
2265{
2266	return B_ERROR;
2267}
2268
2269
2270// TODO: Maybe the following two methods would fit better into InterfaceDefs.cpp
2271// In R5, they do all the work client side, we let the app_server handle the details.
2272status_t
2273set_menu_info(menu_info *info)
2274{
2275	if (!info)
2276		return B_BAD_VALUE;
2277
2278	BPrivate::AppServerLink link;
2279	link.StartMessage(AS_SET_MENU_INFO);
2280	link.Attach<menu_info>(*info);
2281
2282	status_t status = B_ERROR;
2283	if (link.FlushWithReply(status) == B_OK && status == B_OK)
2284		BMenu::sMenuInfo = *info;
2285		// Update also the local copy, in case anyone relies on it
2286
2287	return status;
2288}
2289
2290
2291status_t
2292get_menu_info(menu_info *info)
2293{
2294	if (!info)
2295		return B_BAD_VALUE;
2296
2297	BPrivate::AppServerLink link;
2298	link.StartMessage(AS_GET_MENU_INFO);
2299
2300	status_t status = B_ERROR;
2301	if (link.FlushWithReply(status) == B_OK && status == B_OK)
2302		link.Read<menu_info>(info);
2303
2304	return status;
2305}
2306