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