Menu.cpp revision cb2998af
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 <MenuBar.h>
17#include <MenuItem.h>
18#include <Path.h>
19#include <PropertyInfo.h>
20#include <Screen.h>
21#include <Window.h>
22
23#include <BMCPrivate.h>
24#include <MenuPrivate.h>
25#include <MenuWindow.h>
26
27
28class _ExtraMenuData_ {
29public:
30	menu_tracking_hook trackingHook;
31	void *trackingState;
32
33	_ExtraMenuData_(menu_tracking_hook func, void *state)
34	{
35		trackingHook = func;
36		trackingState = state;
37	};
38};
39
40
41menu_info BMenu::sMenuInfo;
42bool BMenu::sAltAsCommandKey;
43
44static property_info
45sPropList[] = {
46	{ "Enabled", { B_GET_PROPERTY, 0 },
47	{ B_DIRECT_SPECIFIER, 0 }, "Returns true if menu or menu item is enabled; false "
48		"otherwise." },
49
50	{ "Enabled", { B_SET_PROPERTY, 0 },
51	{ B_DIRECT_SPECIFIER, 0 }, "Enables or disables menu or menu item." },
52
53	{ "Label", { B_GET_PROPERTY, 0 },
54	{ B_DIRECT_SPECIFIER, 0 }, "Returns the string label of the menu or menu item." },
55
56	{ "Label", { B_SET_PROPERTY, 0 },
57	{ B_DIRECT_SPECIFIER, 0 }, "Sets the string label of the menu or menu item." },
58
59	{ "Mark", { B_GET_PROPERTY, 0 },
60	{ B_DIRECT_SPECIFIER, 0 }, "Returns true if the menu item or the menu's superitem "
61		"is marked; false otherwise." },
62
63	{ "Mark", { B_SET_PROPERTY, 0 },
64	{ B_DIRECT_SPECIFIER, 0 }, "Marks or unmarks the menu item or the menu's superitem." },
65
66	{ "Menu", { B_CREATE_PROPERTY, 0 },
67	{ B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 },
68		"Adds a new menu item at the specified index with the text label found in \"data\" "
69		"and the int32 command found in \"what\" (used as the what field in the CMessage "
70		"sent by the item)." },
71
72	{ "Menu", { B_DELETE_PROPERTY, 0 },
73	{ B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 },
74		"Removes the selected menu or menus." },
75
76	{ "Menu", { B_GET_PROPERTY, B_SET_PROPERTY, 0 },
77	{ B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 },
78		"Directs scripting message to the specified menu, first popping the current "
79		"specifier off the stack." },
80
81	{ "MenuItem", { B_COUNT_PROPERTIES, 0 },
82	{ B_DIRECT_SPECIFIER, 0 }, "Counts the number of menu items in the specified menu." },
83
84	{ "MenuItem", { B_CREATE_PROPERTY, 0 },
85	{ B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 },
86		"Adds a new menu item at the specified index with the text label found in \"data\" "
87		"and the int32 command found in \"what\" (used as the what field in the CMessage "
88		"sent by the item)." },
89
90	{ "MenuItem", { B_DELETE_PROPERTY, 0 },
91	{ B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 },
92		"Removes the specified menu item from its parent menu." },
93
94	{ "MenuItem", { B_EXECUTE_PROPERTY, 0 },
95	{ B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 },
96		"Invokes the specified menu item." },
97
98	{ "MenuItem", { B_GET_PROPERTY, B_SET_PROPERTY, 0 },
99	{ B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 },
100		"Directs scripting message to the specified menu, first popping the current "
101		"specifier off the stack." },
102	{0}
103};
104
105
106BMenu::BMenu(const char *name, menu_layout layout)
107	:	BView(BRect(0, 0, 0, 0), name, 0, B_WILL_DRAW),
108		fChosenItem(NULL),
109		fPad(14.0f, 2.0f, 20.0f, 0.0f),
110		fSelected(NULL),
111		fCachedMenuWindow(NULL),
112		fSuper(NULL),
113		fSuperitem(NULL),
114		fAscent(-1.0f),
115		fDescent(-1.0f),
116		fFontHeight(-1.0f),
117		fState(0),
118		fLayout(layout),
119		fExtraRect(NULL),
120		fMaxContentWidth(0.0f),
121		fInitMatrixSize(NULL),
122		fExtraMenuData(NULL),
123		fTrigger(0),
124		fResizeToFit(true),
125		fUseCachedMenuLayout(false),
126		fEnabled(true),
127		fDynamicName(false),
128		fRadioMode(false),
129		fTrackNewBounds(false),
130		fStickyMode(false),
131		fIgnoreHidden(true),
132		fTriggerEnabled(true),
133		fRedrawAfterSticky(false),
134		fAttachAborted(false)
135{
136	InitData(NULL);
137}
138
139
140BMenu::BMenu(const char *name, float width, float height)
141	:	BView(BRect(0.0f, width, 0.0f, height), name, 0, B_WILL_DRAW),
142		fChosenItem(NULL),
143		fSelected(NULL),
144		fCachedMenuWindow(NULL),
145		fSuper(NULL),
146		fSuperitem(NULL),
147		fAscent(-1.0f),
148		fDescent(-1.0f),
149		fFontHeight(-1.0f),
150		fState(0),
151		fLayout(B_ITEMS_IN_MATRIX),
152		fExtraRect(NULL),
153		fMaxContentWidth(0.0f),
154		fInitMatrixSize(NULL),
155		fExtraMenuData(NULL),
156		fTrigger(0),
157		fResizeToFit(true),
158		fUseCachedMenuLayout(false),
159		fEnabled(true),
160		fDynamicName(false),
161		fRadioMode(false),
162		fTrackNewBounds(false),
163		fStickyMode(false),
164		fIgnoreHidden(true),
165		fTriggerEnabled(true),
166		fRedrawAfterSticky(false),
167		fAttachAborted(false)
168{
169	InitData(NULL);
170}
171
172
173BMenu::~BMenu()
174{
175	DeleteMenuWindow();
176
177	RemoveItems(0, CountItems(), true);
178
179	delete fInitMatrixSize;
180	delete fExtraMenuData;
181}
182
183
184BMenu::BMenu(BMessage *archive)
185	:	BView(archive)
186{
187	InitData(archive);
188}
189
190
191BArchivable *
192BMenu::Instantiate(BMessage *data)
193{
194	if (validate_instantiation(data, "BMenu"))
195		return new BMenu(data);
196
197	return NULL;
198}
199
200
201status_t
202BMenu::Archive(BMessage *data, bool deep) const
203{
204	status_t err = BView::Archive(data, deep);
205
206	if (err < B_OK)
207		return err;
208
209	if (Layout() != B_ITEMS_IN_ROW) {
210		err = data->AddInt32("_layout", Layout());
211
212		if (err < B_OK)
213			return err;
214	}
215
216	err = data->AddBool("_rsize_to_fit", fResizeToFit);
217
218	if (err < B_OK)
219		return err;
220
221	err = data->AddBool("_disable", !IsEnabled());
222
223	if (err < B_OK)
224		return err;
225
226	err = data->AddBool("_radio", IsRadioMode());
227
228	if (err < B_OK)
229		return err;
230
231	err = data->AddBool("_trig_disabled", AreTriggersEnabled());
232
233	if (err < B_OK)
234		return err;
235
236	err = data->AddBool("_dyn_label", fDynamicName);
237
238	if (err < B_OK)
239		return err;
240
241	err = data->AddFloat("_maxwidth", fMaxContentWidth);
242
243	if (err < B_OK)
244		return err;
245
246	if (deep) {
247		// TODO store items and rects
248	}
249
250	return err;
251}
252
253
254void
255BMenu::AttachedToWindow()
256{
257	BView::AttachedToWindow();
258
259	sAltAsCommandKey = true;
260	key_map *keys = NULL;
261	char *chars = NULL;
262	get_key_map(&keys, &chars);
263	if (keys == NULL || keys->left_command_key != 0x5d || keys->right_command_key != 0x5f)
264		sAltAsCommandKey = false;
265	free(chars);
266	free(keys);
267
268	BMenuItem *superItem = Superitem();
269	BMenu *superMenu = Supermenu();
270	if (AddDynamicItem(B_INITIAL_ADD)) {
271		do {
272			if (superMenu != NULL && !superMenu->OkToProceed(superItem)) {
273				AddDynamicItem(B_ABORT);
274				fAttachAborted = true;
275				break;
276			}
277		} while (AddDynamicItem(B_PROCESSING));
278	}
279
280	if (!fAttachAborted)
281		InvalidateLayout();
282}
283
284
285void
286BMenu::DetachedFromWindow()
287{
288	BView::DetachedFromWindow();
289}
290
291
292bool
293BMenu::AddItem(BMenuItem *item)
294{
295	return AddItem(item, CountItems());
296}
297
298
299bool
300BMenu::AddItem(BMenuItem *item, int32 index)
301{
302	if (fLayout == B_ITEMS_IN_MATRIX)
303		debugger("BMenu::AddItem(BMenuItem *, int32) this method can only "
304				"be called if the menu layout is not B_ITEMS_IN_MATRIX");
305
306	return _AddItem(item, index);
307}
308
309
310bool
311BMenu::AddItem(BMenuItem *item, BRect frame)
312{
313	if (fLayout != B_ITEMS_IN_MATRIX)
314		debugger("BMenu::AddItem(BMenuItem *, BRect) this method can only "
315			"be called if the menu layout is B_ITEMS_IN_MATRIX");
316
317	if (!item)
318		return false;
319
320	item->fBounds = frame;
321
322	return _AddItem(item, CountItems());
323}
324
325
326bool
327BMenu::AddItem(BMenu *submenu)
328{
329	BMenuItem *item = new BMenuItem(submenu);
330	if (!item)
331		return false;
332
333	return _AddItem(item, CountItems());
334}
335
336
337bool
338BMenu::AddItem(BMenu *submenu, int32 index)
339{
340	if (fLayout == B_ITEMS_IN_MATRIX)
341		debugger("BMenu::AddItem(BMenuItem *, int32) this method can only "
342				"be called if the menu layout is not B_ITEMS_IN_MATRIX");
343
344	BMenuItem *item = new BMenuItem(submenu);
345	if (!item)
346		return false;
347
348	return _AddItem(item, index);
349}
350
351
352bool
353BMenu::AddItem(BMenu *submenu, BRect frame)
354{
355	if (fLayout != B_ITEMS_IN_MATRIX)
356		debugger("BMenu::AddItem(BMenu *, BRect) this method can only "
357			"be called if the menu layout is B_ITEMS_IN_MATRIX");
358
359	BMenuItem *item = new BMenuItem(submenu);
360	item->fBounds = frame;
361
362	return _AddItem(item, CountItems());
363}
364
365
366bool
367BMenu::AddList(BList *list, int32 index)
368{
369	// TODO: test this function, it's not documented in the bebook.
370	if (list == NULL)
371		return false;
372
373	int32 numItems = list->CountItems();
374	for (int32 i = 0; i < numItems; i++) {
375		BMenuItem *item = static_cast<BMenuItem *>(list->ItemAt(i));
376		if (item != NULL)
377			_AddItem(item, index + i);
378	}
379
380	return true;
381}
382
383
384bool
385BMenu::AddSeparatorItem()
386{
387	BMenuItem *item = new BSeparatorItem();
388	return _AddItem(item, CountItems());
389}
390
391
392bool
393BMenu::RemoveItem(BMenuItem *item)
394{
395	// TODO: Check if item is also deleted
396	return RemoveItems(0, 0, item, false);
397}
398
399
400BMenuItem *
401BMenu::RemoveItem(int32 index)
402{
403	BMenuItem *item = ItemAt(index);
404	RemoveItems(0, 0, item, false);
405	return item;
406}
407
408
409bool
410BMenu::RemoveItems(int32 index, int32 count, bool del)
411{
412	return RemoveItems(index, count, NULL, del);
413}
414
415
416bool
417BMenu::RemoveItem(BMenu *submenu)
418{
419	for (int32 i = 0; i < fItems.CountItems(); i++) {
420		if (static_cast<BMenuItem *>(fItems.ItemAtFast(i))->Submenu() == submenu)
421			return RemoveItems(i, 1, NULL, false);
422	}
423
424	return false;
425}
426
427
428int32
429BMenu::CountItems() const
430{
431	return fItems.CountItems();
432}
433
434
435BMenuItem *
436BMenu::ItemAt(int32 index) const
437{
438	return static_cast<BMenuItem *>(fItems.ItemAt(index));
439}
440
441
442BMenu *
443BMenu::SubmenuAt(int32 index) const
444{
445	BMenuItem *item = static_cast<BMenuItem *>(fItems.ItemAt(index));
446	return (item != NULL) ? item->Submenu() : NULL;
447}
448
449
450int32
451BMenu::IndexOf(BMenuItem *item) const
452{
453	return fItems.IndexOf(item);
454}
455
456
457int32
458BMenu::IndexOf(BMenu *submenu) const
459{
460	for (int32 i = 0; i < fItems.CountItems(); i++) {
461		if (ItemAt(i)->Submenu() == submenu)
462			return i;
463	}
464
465	return -1;
466}
467
468
469BMenuItem *
470BMenu::FindItem(const char *label) const
471{
472	BMenuItem *item = NULL;
473
474	for (int32 i = 0; i < CountItems(); i++) {
475		item = ItemAt(i);
476
477		if (item->Label() && strcmp(item->Label(), label) == 0)
478			return item;
479
480		if (item->Submenu() != NULL) {
481			item = item->Submenu()->FindItem(label);
482			if (item != NULL)
483				return item;
484		}
485	}
486
487	return NULL;
488}
489
490
491BMenuItem *
492BMenu::FindItem(uint32 command) const
493{
494	BMenuItem *item = NULL;
495
496	for (int32 i = 0; i < CountItems(); i++) {
497		item = ItemAt(i);
498
499		if (item->Command() == command)
500			return item;
501
502		if (item->Submenu() != NULL) {
503			item = item->Submenu()->FindItem(command);
504			if (item != NULL)
505				return item;
506		}
507	}
508
509	return NULL;
510}
511
512
513status_t
514BMenu::SetTargetForItems(BHandler *handler)
515{
516	status_t status = B_OK;
517	for (int32 i = 0; i < fItems.CountItems(); i++) {
518		status = ItemAt(i)->SetTarget(handler);
519		if (status < B_OK)
520			break;
521	}
522
523	return status;
524}
525
526
527status_t
528BMenu::SetTargetForItems(BMessenger messenger)
529{
530	status_t status = B_OK;
531	for (int32 i = 0; i < fItems.CountItems(); i++) {
532		status = ItemAt(i)->SetTarget(messenger);
533		if (status < B_OK)
534			break;
535	}
536
537	return status;
538}
539
540
541void
542BMenu::SetEnabled(bool enabled)
543{
544	if (fEnabled == enabled)
545		return;
546
547	fEnabled = enabled;
548
549	if (fSuperitem)
550		fSuperitem->SetEnabled(enabled);
551}
552
553
554void
555BMenu::SetRadioMode(bool flag)
556{
557	fRadioMode = flag;
558	if (!flag)
559		SetLabelFromMarked(false);
560}
561
562
563void
564BMenu::SetTriggersEnabled(bool flag)
565{
566	fTriggerEnabled = flag;
567}
568
569
570void
571BMenu::SetMaxContentWidth(float width)
572{
573	fMaxContentWidth = width;
574}
575
576
577void
578BMenu::SetLabelFromMarked(bool flag)
579{
580	fDynamicName = flag;
581	if (flag)
582		SetRadioMode(true);
583}
584
585
586bool
587BMenu::IsLabelFromMarked()
588{
589	return fDynamicName;
590}
591
592
593bool
594BMenu::IsEnabled() const
595{
596	if (!fEnabled)
597		return false;
598
599	return fSuper ? fSuper->IsEnabled() : true ;
600}
601
602
603bool
604BMenu::IsRadioMode() const
605{
606	return fRadioMode;
607}
608
609
610bool
611BMenu::AreTriggersEnabled() const
612{
613	return fTriggerEnabled;
614}
615
616
617bool
618BMenu::IsRedrawAfterSticky() const
619{
620	return fRedrawAfterSticky;
621}
622
623
624float
625BMenu::MaxContentWidth() const
626{
627	return fMaxContentWidth;
628}
629
630
631BMenuItem *
632BMenu::FindMarked()
633{
634	for (int32 i = 0; i < fItems.CountItems(); i++) {
635		BMenuItem *item = ItemAt(i);
636		if (item->IsMarked())
637			return item;
638	}
639
640	return NULL;
641}
642
643
644BMenu *
645BMenu::Supermenu() const
646{
647	return fSuper;
648}
649
650
651BMenuItem *
652BMenu::Superitem() const
653{
654	return fSuperitem;
655}
656
657
658void
659BMenu::MessageReceived(BMessage *msg)
660{
661	BView::MessageReceived(msg);
662}
663
664
665void
666BMenu::KeyDown(const char *bytes, int32 numBytes)
667{
668	switch (bytes[0]) {
669		case B_UP_ARROW:
670		{
671			if (fSelected) 	{
672				fSelected->fSelected = false;
673
674				if (fSelected == fItems.FirstItem())
675					fSelected = static_cast<BMenuItem *>(fItems.LastItem());
676				else
677					fSelected = ItemAt(IndexOf(fSelected) - 1);
678			} else
679				fSelected = static_cast<BMenuItem *>(fItems.LastItem());
680
681			fSelected->fSelected = true;
682
683			break;
684		}
685		case B_DOWN_ARROW:
686		{
687			if (fSelected) {
688				fSelected->fSelected = false;
689
690				if (fSelected == fItems.LastItem())
691					fSelected = static_cast<BMenuItem *>(fItems.FirstItem());
692				else
693					fSelected = ItemAt(IndexOf(fSelected) + 1);
694			} else
695				fSelected = static_cast<BMenuItem *>(fItems.FirstItem());
696
697			fSelected->fSelected = true;
698
699			break;
700		}
701		case B_HOME:
702		{
703			if (fSelected)
704				fSelected->fSelected = false;
705
706			fSelected = static_cast<BMenuItem *>(fItems.FirstItem());
707			fSelected->fSelected = true;
708
709			break;
710		}
711		case B_END:
712		{
713			if (fSelected)
714				fSelected->fSelected = false;
715
716			fSelected = static_cast<BMenuItem *>(fItems.LastItem());
717			fSelected->fSelected = true;
718
719			break;
720		}
721		case B_ENTER:
722		case B_SPACE:
723		{
724			if (fSelected)
725				InvokeItem(fSelected);
726
727			break;
728		}
729		default:
730			BView::KeyDown(bytes, numBytes);
731	}
732}
733
734
735void
736BMenu::Draw(BRect updateRect)
737{
738	DrawBackground(updateRect);
739	DrawItems(updateRect);
740}
741
742
743void
744BMenu::GetPreferredSize(float *_width, float *_height)
745{
746	ComputeLayout(0, true, false, _width, _height);
747}
748
749
750void
751BMenu::ResizeToPreferred()
752{
753	BView::ResizeToPreferred();
754}
755
756
757void
758BMenu::FrameMoved(BPoint new_position)
759{
760	BView::FrameMoved(new_position);
761}
762
763
764void
765BMenu::FrameResized(float new_width, float new_height)
766{
767	BView::FrameResized(new_width, new_height);
768}
769
770
771void
772BMenu::InvalidateLayout()
773{
774	CacheFontInfo();
775	LayoutItems(0);
776	Invalidate();
777}
778
779
780BHandler *
781BMenu::ResolveSpecifier(BMessage *msg, int32 index, BMessage *specifier,
782						int32 form, const char *property)
783{
784	BPropertyInfo propInfo(sPropList);
785	BHandler *target = NULL;
786
787	switch (propInfo.FindMatch(msg, 0, specifier, form, property)) {
788		case B_ERROR:
789			break;
790
791		case 0:
792		case 1:
793		case 2:
794		case 3:
795		case 4:
796		case 5:
797		case 6:
798		case 7:
799			target = this;
800			break;
801		case 8:
802			// TODO: redirect to menu
803			target = this;
804			break;
805		case 9:
806		case 10:
807		case 11:
808		case 12:
809			target = this;
810			break;
811		case 13:
812			// TODO: redirect to menuitem
813			target = this;
814			break;
815	}
816
817	if (!target)
818		target = BView::ResolveSpecifier(msg, index, specifier, form,
819		property);
820
821	return target;
822}
823
824
825status_t
826BMenu::GetSupportedSuites(BMessage *data)
827{
828	if (data == NULL)
829		return B_BAD_VALUE;
830
831	status_t err = data->AddString("suites", "suite/vnd.Be-menu");
832
833	if (err < B_OK)
834		return err;
835
836	BPropertyInfo propertyInfo(sPropList);
837	err = data->AddFlat("messages", &propertyInfo);
838
839	if (err < B_OK)
840		return err;
841
842	return BView::GetSupportedSuites(data);
843}
844
845
846status_t
847BMenu::Perform(perform_code d, void *arg)
848{
849	return BView::Perform(d, arg);
850}
851
852
853void
854BMenu::MakeFocus(bool focused)
855{
856	BView::MakeFocus(focused);
857}
858
859
860void
861BMenu::AllAttached()
862{
863	BView::AllAttached();
864}
865
866
867void
868BMenu::AllDetached()
869{
870	BView::AllDetached();
871}
872
873
874BMenu::BMenu(BRect frame, const char *name, uint32 resizingMode, uint32 flags,
875			 menu_layout layout, bool resizeToFit)
876	:	BView(frame, name, resizingMode, flags),
877		fChosenItem(NULL),
878		fSelected(NULL),
879		fCachedMenuWindow(NULL),
880		fSuper(NULL),
881		fSuperitem(NULL),
882		fAscent(-1.0f),
883		fDescent(-1.0f),
884		fFontHeight(-1.0f),
885		fState(0),
886		fLayout(layout),
887		fExtraRect(NULL),
888		fMaxContentWidth(0.0f),
889		fInitMatrixSize(NULL),
890		fExtraMenuData(NULL),
891		fTrigger(0),
892		fResizeToFit(resizeToFit),
893		fUseCachedMenuLayout(false),
894		fEnabled(true),
895		fDynamicName(false),
896		fRadioMode(false),
897		fTrackNewBounds(false),
898		fStickyMode(false),
899		fIgnoreHidden(true),
900		fTriggerEnabled(true),
901		fRedrawAfterSticky(false),
902		fAttachAborted(false)
903{
904	InitData(NULL);
905}
906
907
908void
909BMenu::SetItemMargins(float left, float top, float right, float bottom)
910{
911	fPad.Set(left, top, right, bottom);
912}
913
914
915void
916BMenu::GetItemMargins(float *left, float *top, float *right,
917						   float *bottom) const
918{
919	if (left != NULL)
920		*left = fPad.left;
921	if (top != NULL)
922		*top = fPad.top;
923	if (right != NULL)
924		*right = fPad.right;
925	if (bottom != NULL)
926		*bottom = fPad.bottom;
927}
928
929
930menu_layout
931BMenu::Layout() const
932{
933	return fLayout;
934}
935
936
937void
938BMenu::Show()
939{
940	Show(false);
941}
942
943
944void
945BMenu::Show(bool selectFirst)
946{
947	_show(selectFirst);
948}
949
950
951void
952BMenu::Hide()
953{
954	_hide();
955}
956
957
958BMenuItem *
959BMenu::Track(bool openAnyway, BRect *clickToOpenRect)
960{
961	if (!IsStickyPrefOn())
962		openAnyway = false;
963
964	SetStickyMode(openAnyway);
965
966	if (openAnyway && LockLooper()) {
967		RedrawAfterSticky(Bounds());
968		UnlockLooper();
969	}
970
971	if (clickToOpenRect != NULL && LockLooper()) {
972		fExtraRect = clickToOpenRect;
973		ConvertFromScreen(fExtraRect);
974		UnlockLooper();
975	}
976
977	int action;
978	BMenuItem *menuItem = _track(&action, -1);
979
980	SetStickyMode(false);
981	fExtraRect = NULL;
982
983	return menuItem;
984}
985
986
987bool
988BMenu::AddDynamicItem(add_state s)
989{
990	// Implemented in subclasses
991	return false;
992}
993
994
995void
996BMenu::DrawBackground(BRect update)
997{
998	rgb_color oldColor = HighColor();
999	SetHighColor(sMenuInfo.background_color);
1000	FillRect(Bounds() & update, B_SOLID_HIGH);
1001	SetHighColor(oldColor);
1002}
1003
1004
1005void
1006BMenu::SetTrackingHook(menu_tracking_hook func, void *state)
1007{
1008	delete fExtraMenuData;
1009	fExtraMenuData = new _ExtraMenuData_(func, state);
1010}
1011
1012
1013void BMenu::_ReservedMenu3() {}
1014void BMenu::_ReservedMenu4() {}
1015void BMenu::_ReservedMenu5() {}
1016void BMenu::_ReservedMenu6() {}
1017
1018
1019BMenu &
1020BMenu::operator=(const BMenu &)
1021{
1022	return *this;
1023}
1024
1025
1026void
1027BMenu::InitData(BMessage *data)
1028{
1029	// TODO: Get _color, _fname, _fflt from the message, if present
1030	BFont font;
1031	font.SetFamilyAndStyle(sMenuInfo.f_family, sMenuInfo.f_style);
1032	font.SetSize(sMenuInfo.font_size);
1033	SetFont(&font, B_FONT_FAMILY_AND_STYLE | B_FONT_SIZE);
1034
1035	SetLowColor(sMenuInfo.background_color);
1036	SetViewColor(sMenuInfo.background_color);
1037
1038	if (data != NULL) {
1039		data->FindInt32("_layout", (int32 *)&fLayout);
1040		data->FindBool("_rsize_to_fit", &fResizeToFit);
1041		data->FindBool("_disable", &fEnabled);
1042		data->FindBool("_radio", &fRadioMode);
1043
1044		bool disableTrigger = false;
1045		data->FindBool("_trig_disabled", &disableTrigger);
1046		fTriggerEnabled = !disableTrigger;
1047
1048		data->FindBool("_dyn_label", &fDynamicName);
1049		data->FindFloat("_maxwidth", &fMaxContentWidth);
1050	}
1051}
1052
1053
1054bool
1055BMenu::_show(bool selectFirstItem)
1056{
1057	// See if the supermenu has a cached menuwindow,
1058	// and use that one if possible.
1059	BMenuWindow *window = NULL;
1060	bool ourWindow = false;
1061	if (fSuper != NULL) {
1062		fSuperbounds = fSuper->ConvertToScreen(fSuper->Bounds());
1063		window = fSuper->MenuWindow();
1064	}
1065
1066	// Otherwise, create a new one
1067	// This happens for "stand alone" BPopUpMenus
1068	// (i.e. not within a BMenuField)
1069	if (window == NULL) {
1070		// Menu windows get the BMenu's handler name
1071		window = new BMenuWindow(Name());
1072		ourWindow = true;
1073	}
1074
1075	if (window == NULL)
1076		return false;
1077
1078	if (window->Lock()) {
1079		fAttachAborted = false;
1080		window->AttachMenu(this);
1081
1082		// Menu didn't have the time to add its items: aborting...
1083		if (fAttachAborted) {
1084			window->DetachMenu();
1085			// TODO: Probably not needed, we can just let _hide() quit the window
1086			if (ourWindow)
1087				window->Quit();
1088			else
1089				window->Unlock();
1090			return false;
1091		}
1092
1093		// Move the BMenu to 1, 1, if it's attached to a BMenuWindow,
1094		// (that means it's a BMenu, BMenuBars are attached to regular BWindows).
1095		// This is needed to be able to draw the frame around the BMenu.
1096		if (dynamic_cast<BMenuWindow *>(window) != NULL)
1097			MoveTo(1, 1);
1098
1099		// TODO: for some reason, Window() can already be NULL at this point,
1100		//		which causes a crash in one of the following functions...
1101		// Does this still happens ?
1102		UpdateWindowViewSize();
1103		window->Show();
1104
1105		if (selectFirstItem)
1106			SelectItem(ItemAt(0));
1107
1108		window->Unlock();
1109	}
1110
1111	return true;
1112}
1113
1114
1115void
1116BMenu::_hide()
1117{
1118	BMenuWindow *window = static_cast<BMenuWindow *>(Window());
1119	if (window == NULL || !window->Lock())
1120		return;
1121
1122	SelectItem(NULL);
1123
1124	window->Hide();
1125	window->DetachMenu();
1126		// we don't want to be deleted when the window is removed
1127
1128	// Delete the menu window used by our submenus
1129	DeleteMenuWindow();
1130
1131	window->Unlock();
1132
1133	if (fSuper == NULL && window->Lock())
1134		window->Quit();
1135}
1136
1137
1138BMenuItem *
1139BMenu::_track(int *action, long start)
1140{
1141	// TODO: Take Sticky mode into account, cleanup
1142	ulong buttons;
1143	BMenuItem *item = NULL;
1144	int localAction = MENU_ACT_NONE;
1145
1146	bigtime_t startTime = system_time();
1147	bigtime_t delay = 200000; // TODO: Test and reduce if needed.
1148
1149	do {
1150		if (!LockLooper())
1151			break;
1152
1153		bigtime_t snoozeAmount = 50000;
1154		BPoint location;
1155		GetMouse(&location, &buttons, false);
1156		BPoint screenLocation = ConvertToScreen(location);
1157		item = HitTestItems(location, B_ORIGIN);
1158		if (item != NULL) {
1159			if (item != fSelected) {
1160				SelectItem(item, -1);
1161				startTime = system_time();
1162				snoozeAmount = 20000;
1163			} else if (system_time() > delay + startTime && item->Submenu()
1164				&& item->Submenu()->Window() == NULL) {
1165				// Open the submenu if it's not opened yet, but only if
1166				// the mouse pointer stayed over there for some time
1167				// (hysteresis)
1168				SelectItem(item);
1169			}
1170		} else {
1171			if (OverSuper(screenLocation)) {
1172				UnlockLooper();
1173				break;
1174			}
1175			if (fSelected != NULL && !OverSubmenu(fSelected, screenLocation))
1176				SelectItem(NULL);
1177		}
1178
1179		if (fSelected != NULL && OverSubmenu(fSelected, screenLocation)) {
1180			UnlockLooper();
1181
1182			int submenuAction = MENU_ACT_NONE;
1183			BMenuItem *submenuItem = fSelected->Submenu()->_track(&submenuAction);
1184			if (submenuAction == MENU_ACT_CLOSE) {
1185				item = submenuItem;
1186				localAction = submenuAction;
1187				break;
1188			}
1189
1190			if (!LockLooper())
1191				break;
1192		}
1193
1194		UnlockLooper();
1195
1196		snooze(snoozeAmount);
1197	} while (buttons != 0);
1198
1199	if (localAction == MENU_ACT_NONE) {
1200		if (buttons != 0)
1201			localAction = MENU_ACT_NONE;
1202		else
1203			localAction = MENU_ACT_CLOSE;
1204	}
1205
1206	if (action != NULL)
1207		*action = localAction;
1208
1209	if (LockLooper()) {
1210		SelectItem(NULL);
1211		UnlockLooper();
1212	}
1213
1214	// delete the menu window recycled for all the child menus
1215	DeleteMenuWindow();
1216
1217	return item;
1218}
1219
1220
1221bool
1222BMenu::_AddItem(BMenuItem *item, int32 index)
1223{
1224	ASSERT(item != NULL);
1225
1226	bool locked = LockLooper();
1227
1228	if (!fItems.AddItem(item, index)) {
1229		if (locked)
1230			UnlockLooper();
1231		return false;
1232	}
1233
1234	// install the item on the supermenu's window
1235	// or onto our window, if we are a root menu
1236	BWindow* window = NULL;
1237	if (Superitem() != NULL)
1238		window = Superitem()->fWindow;
1239	else
1240		window = Window();
1241	if (window != NULL)
1242		item->Install(window);
1243
1244	item->SetSuper(this);
1245
1246	if (locked && window != NULL && !window->IsHidden()) {
1247		// Make sure we update the layout if needed.
1248		LayoutItems(index);
1249		//UpdateWindowViewSize();
1250		Invalidate();
1251	}
1252
1253	if (locked)
1254		UnlockLooper();
1255
1256	return true;
1257}
1258
1259
1260bool
1261BMenu::RemoveItems(int32 index, int32 count, BMenuItem *item, bool deleteItems)
1262{
1263	bool success = false;
1264	bool invalidateLayout = false;
1265
1266	bool locked = LockLooper();
1267	BWindow *window = Window();
1268
1269	// The plan is simple: If we're given a BMenuItem directly, we use it
1270	// and ignore index and count. Otherwise, we use them instead.
1271	if (item != NULL) {
1272		if (fItems.RemoveItem(item)) {
1273			if (item == fSelected && window != NULL)
1274				SelectItem(NULL);
1275			item->Uninstall();
1276			item->SetSuper(NULL);
1277			if (deleteItems)
1278				delete item;
1279			success = invalidateLayout = true;
1280		}
1281	} else {
1282		// We iterate backwards because it's simpler
1283		int32 i = min_c(index + count - 1, fItems.CountItems() - 1);
1284		// NOTE: the range check for "index" is done after
1285		// calculating the last index to be removed, so
1286		// that the range is not "shifted" unintentionally
1287		index = max_c(0, index);
1288		for (; i >= index; i--) {
1289			item = static_cast<BMenuItem*>(fItems.ItemAt(i));
1290			if (item != NULL) {
1291				if (fItems.RemoveItem(item)) {
1292					if (item == fSelected && window != NULL)
1293						SelectItem(NULL);
1294					item->Uninstall();
1295					item->SetSuper(NULL);
1296					if (deleteItems)
1297						delete item;
1298					success = true;
1299					invalidateLayout = true;
1300				} else {
1301					// operation not entirely successful
1302					success = false;
1303					break;
1304				}
1305			}
1306		}
1307	}
1308
1309	if (invalidateLayout && locked && window != NULL && !window->IsHidden()) {
1310		LayoutItems(0);
1311		//UpdateWindowViewSize();
1312		Invalidate();
1313	}
1314
1315	if (locked)
1316		UnlockLooper();
1317
1318	return success;
1319}
1320
1321
1322void
1323BMenu::LayoutItems(int32 index)
1324{
1325	CalcTriggers();
1326
1327	float width, height;
1328	ComputeLayout(index, fResizeToFit, true, &width, &height);
1329
1330	ResizeTo(width, height);
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);
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->SetAutomaticTrigger(*newTrigger);
1809		}
1810	}
1811}
1812
1813
1814const char *
1815BMenu::ChooseTrigger(const char *title, BList *chars)
1816{
1817	ASSERT(chars != NULL);
1818
1819	if (title == NULL)
1820		return NULL;
1821
1822	char *titlePtr = const_cast<char *>(title);
1823
1824	char trigger;
1825	// TODO: Oh great, reinterpret_cast all around
1826	while ((trigger = *titlePtr) != '\0') {
1827		if (!chars->HasItem(reinterpret_cast<void *>((uint32)trigger)))	{
1828			chars->AddItem(reinterpret_cast<void *>((uint32)trigger));
1829			return titlePtr;
1830		}
1831
1832		titlePtr++;
1833	}
1834
1835	return NULL;
1836}
1837
1838
1839void
1840BMenu::UpdateWindowViewSize(bool upWind)
1841{
1842	BWindow *window = Window();
1843
1844	ASSERT(window != NULL);
1845
1846	if (window == NULL)
1847		return;
1848
1849	bool scroll;
1850	BRect frame = CalcFrame(ScreenLocation(), &scroll);
1851	ResizeTo(frame.Width(), frame.Height());
1852
1853	if (fItems.CountItems() > 0)
1854		window->ResizeTo(Bounds().Width() + 2, Bounds().Height() + 2);
1855	else {
1856		CacheFontInfo();
1857		window->ResizeTo(StringWidth(kEmptyMenuLabel) + 5, fFontHeight + 6);
1858	}
1859	window->MoveTo(frame.LeftTop());
1860}
1861
1862
1863bool
1864BMenu::IsStickyPrefOn()
1865{
1866	return sMenuInfo.click_to_open;
1867}
1868
1869
1870void
1871BMenu::RedrawAfterSticky(BRect bounds)
1872{
1873}
1874
1875
1876bool
1877BMenu::OkToProceed(BMenuItem* item)
1878{
1879	bool proceed = true;
1880	BPoint where;
1881	ulong buttons;
1882	GetMouse(&where, &buttons, false);
1883	// Quit if user releases the mouse button or moves
1884	// the pointer over another item
1885	if (buttons == 0 || HitTestItems(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