Menu.cpp revision 1664b981
1//------------------------------------------------------------------------------
2//	Copyright (c) 2001-2005, Haiku, Inc.
3//
4//	Permission is hereby granted, free of charge, to any person obtaining a
5//	copy of this software and associated documentation files (the "Software"),
6//	to deal in the Software without restriction, including without limitation
7//	the rights to use, copy, modify, merge, publish, distribute, sublicense,
8//	and/or sell copies of the Software, and to permit persons to whom the
9//	Software is furnished to do so, subject to the following conditions:
10//
11//	The above copyright notice and this permission notice shall be included in
12//	all copies or substantial portions of the Software.
13//
14//	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15//	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16//	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17//	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18//	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19//	FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20//	DEALINGS IN THE SOFTWARE.
21//
22//	File Name:		Menu.cpp
23//	Authors:		Marc Flerackers (mflerackers@androme.be)
24//					Stefano Ceccherini (burton666@libero.it)
25//	Description:	BMenu display a menu of selectable items.
26//------------------------------------------------------------------------------
27#include <File.h>
28#include <FindDirectory.h>
29#include <Menu.h>
30#include <MenuItem.h>
31#include <Path.h>
32#include <PropertyInfo.h>
33#include <Window.h>
34
35#include <MenuWindow.h>
36
37#ifndef COMPILE_FOR_R5
38menu_info BMenu::sMenuInfo;
39#endif
40
41static property_info
42sPropList[] = {
43	{ "Enabled", { B_GET_PROPERTY, 0 },
44		{ B_DIRECT_SPECIFIER, 0 }, "Returns true if menu or menu item is enabled; false "
45		"otherwise." },
46	{ "Enabled", { B_SET_PROPERTY, 0 },
47		{ B_DIRECT_SPECIFIER, 0 }, "Enables or disables menu or menu item." },
48	{ "Label", { B_GET_PROPERTY, 0 },
49		{ B_DIRECT_SPECIFIER, 0 }, "Returns the string label of the menu or menu item." },
50	{ "Label", { B_SET_PROPERTY, 0 },
51		{ B_DIRECT_SPECIFIER, 0 }, "Sets the string label of the menu or menu item." },
52	{ "Mark", { B_GET_PROPERTY, 0 },
53		{ B_DIRECT_SPECIFIER, 0 }, "Returns true if the menu item or the menu's superitem "
54		"is marked; false otherwise." },
55	{ "Mark", { B_SET_PROPERTY, 0 },
56		{ B_DIRECT_SPECIFIER, 0 }, "Marks or unmarks the menu item or the menu's superitem." },
57	{ "Menu", { B_CREATE_PROPERTY, 0 },
58		{ B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 },
59		"Adds a new menu item at the specified index with the text label found in \"data\" "
60		"and the int32 command found in \"what\" (used as the what field in the CMessage "
61		"sent by the item)." },
62	{ "Menu", { B_DELETE_PROPERTY, 0 },
63		{ B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 },
64		"Removes the selected menu or menus." },
65	{ "Menu", { B_GET_PROPERTY, B_SET_PROPERTY, 0 },
66		{ B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 },
67		"Directs scripting message to the specified menu, first popping the current "
68		"specifier off the stack." },
69	{ "MenuItem", { B_COUNT_PROPERTIES, 0 },
70		{ B_DIRECT_SPECIFIER, 0 }, "Counts the number of menu items in the specified menu." },
71	{ "MenuItem", { B_CREATE_PROPERTY, 0 },
72		{ B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 },
73		"Adds a new menu item at the specified index with the text label found in \"data\" "
74		"and the int32 command found in \"what\" (used as the what field in the CMessage "
75		"sent by the item)." },
76	{ "MenuItem", { B_DELETE_PROPERTY, 0 },
77		{ B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 },
78		"Removes the specified menu item from its parent menu." },
79	{ "MenuItem", { B_EXECUTE_PROPERTY, 0 },
80		{ B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 },
81		"Invokes the specified menu item." },
82	{ "MenuItem", { B_GET_PROPERTY, B_SET_PROPERTY, 0 },
83		{ B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 },
84		"Directs scripting message to the specified menu, first popping the current "
85		"specifier off the stack." },
86	{}
87};
88
89
90BMenu::BMenu(const char *name, menu_layout layout)
91	:	BView(BRect(), name, B_FOLLOW_LEFT | B_FOLLOW_TOP,
92			B_WILL_DRAW | B_NAVIGABLE),
93		fChosenItem(NULL),
94		fPad(14.0f, 2.0f, 20.0f, 0.0f),
95		fSelected(NULL),
96		fCachedMenuWindow(NULL),
97		fSuper(NULL),
98		fSuperitem(NULL),
99		fAscent(-1.0f),
100		fDescent(-1.0f),
101		fFontHeight(-1.0f),
102		fState(0),
103		fLayout(layout),
104		fExtraRect(NULL),
105		fMaxContentWidth(0.0f),
106		fInitMatrixSize(NULL),
107		fExtraMenuData(NULL),
108		fTrigger(0),
109		fResizeToFit(true),
110		fUseCachedMenuLayout(true),
111		fEnabled(true),
112		fDynamicName(false),
113		fRadioMode(false),
114		fTrackNewBounds(false),
115		fStickyMode(false),
116		fIgnoreHidden(true),
117		fTriggerEnabled(true),
118		fRedrawAfterSticky(false),
119		fAttachAborted(false)
120{
121	InitData(NULL);
122}
123
124
125BMenu::BMenu(const char *name, float width, float height)
126	:	BView(BRect(0.0f, width, 0.0f, height), name,
127			B_FOLLOW_LEFT | B_FOLLOW_TOP, B_WILL_DRAW | B_NAVIGABLE),
128		fChosenItem(NULL),
129		fSelected(NULL),
130		fCachedMenuWindow(NULL),
131		fSuper(NULL),
132		fSuperitem(NULL),
133		fAscent(-1.0f),
134		fDescent(-1.0f),
135		fFontHeight(-1.0f),
136		fState(0),
137		fLayout(B_ITEMS_IN_MATRIX),
138		fExtraRect(NULL),
139		fMaxContentWidth(0.0f),
140		fInitMatrixSize(NULL),
141		fExtraMenuData(NULL),
142		fTrigger(0),
143		fResizeToFit(true),
144		fUseCachedMenuLayout(true),
145		fEnabled(true),
146		fDynamicName(false),
147		fRadioMode(false),
148		fTrackNewBounds(false),
149		fStickyMode(false),
150		fIgnoreHidden(true),
151		fTriggerEnabled(true),
152		fRedrawAfterSticky(false),
153		fAttachAborted(false)
154{
155	InitData(NULL);
156}
157
158
159BMenu::~BMenu()
160{
161	RemoveItems(0, CountItems(), true);
162}
163
164
165BMenu::BMenu(BMessage *archive)
166	:	BView(archive)
167{
168	InitData(archive);
169}
170
171
172BArchivable *
173BMenu::Instantiate(BMessage *data)
174{
175	if (validate_instantiation(data, "BMenu"))
176		return new BMenu(data);
177	else
178		return NULL;
179}
180
181
182status_t
183BMenu::Archive(BMessage *data, bool deep) const
184{
185	status_t err = BView::Archive(data, deep);
186
187	if (err < B_OK)
188		return err;
189
190	if (Layout() != B_ITEMS_IN_ROW) {
191		err = data->AddInt32("_layout", Layout());
192
193		if (err != B_OK)
194			return err;
195	}
196
197	err = data->AddBool("_rsize_to_fit", fResizeToFit);
198
199	if (err < B_OK)
200		return err;
201
202	err = data->AddBool("_disable", !IsEnabled());
203
204	if (err < B_OK)
205		return err;
206
207	err = data->AddBool("_radio", IsRadioMode());
208
209	if (err < B_OK)
210		return err;
211
212	err = data->AddBool("_trig_disabled", AreTriggersEnabled());
213
214	if (err < B_OK)
215		return err;
216
217	err = data->AddBool("_dyn_label", fDynamicName);
218
219	if (err < B_OK)
220		return err;
221
222	err = data->AddFloat("_maxwidth", fMaxContentWidth);
223
224	if (err < B_OK)
225		return err;
226
227	if (deep) {
228		// TODO store items and rects
229	}
230
231	return err;
232}
233
234
235void
236BMenu::AttachedToWindow()
237{
238	BView::AttachedToWindow();
239
240	InvalidateLayout();
241}
242
243
244void
245BMenu::DetachedFromWindow()
246{
247	BView::DetachedFromWindow();
248}
249
250
251bool
252BMenu::AddItem(BMenuItem *item)
253{
254	return AddItem(item, CountItems());
255}
256
257
258bool
259BMenu::AddItem(BMenuItem *item, int32 index)
260{
261	item->fSuper = this;
262
263	bool err = fItems.AddItem(item, index);
264
265	if (!err)
266		return err;
267
268	// Make sure we update the layout in case we are already attached.
269	if (Window() && fResizeToFit) {
270		LayoutItems(index);
271		Invalidate();
272	}
273
274	// Find the root menu window, so we can install this item.
275	BMenu *root = this;
276	while (root->Supermenu())
277		root = root->Supermenu();
278
279	if (root->Window())
280		Install(root->Window());
281
282	return err;
283}
284
285
286bool
287BMenu::AddItem(BMenuItem *item, BRect frame)
288{
289	if (fLayout != B_ITEMS_IN_MATRIX)
290		debugger("BMenu::AddItem(BMenuItem *, BRect) this method can only"
291			" be called if the menu layout is B_ITEMS_IN_MATRIX");
292
293	if (!item)
294		return false;
295
296	item->fBounds = frame;
297
298	return AddItem(item, CountItems());
299}
300
301
302bool
303BMenu::AddItem(BMenu *submenu)
304{
305	BMenuItem *item = new BMenuItem(submenu);
306	if (!item)
307		return false;
308
309	submenu->fSuper = this;
310
311	return AddItem(item);
312}
313
314
315bool
316BMenu::AddItem(BMenu *submenu, int32 index)
317{
318	BMenuItem *item = new BMenuItem(submenu);
319	if (!item)
320		return false;
321
322	submenu->fSuper = this;
323
324	return AddItem(item, index);
325}
326
327
328bool
329BMenu::AddItem(BMenu *submenu, BRect frame)
330{
331	if (fLayout != B_ITEMS_IN_MATRIX)
332		debugger("BMenu::AddItem(BMenu *, BRect) this method can only"
333			" be called if the menu layout is B_ITEMS_IN_MATRIX");
334
335	BMenuItem *item = new BMenuItem(submenu);
336	submenu->fSuper = this;
337	item->fBounds = frame;
338
339	return AddItem(item);
340}
341
342
343bool
344BMenu::AddList(BList *list, int32 index)
345{
346	return false;
347}
348
349
350bool
351BMenu::AddSeparatorItem()
352{
353	BMenuItem *item = new BSeparatorItem();
354	item->fSuper = this;
355
356	return fItems.AddItem(item);
357}
358
359
360bool
361BMenu::RemoveItem(BMenuItem *item)
362{
363	// TODO: Check if item is also deleted
364	return RemoveItems(0, 0, item, false);
365}
366
367
368BMenuItem *
369BMenu::RemoveItem(int32 index)
370{
371	BMenuItem *item = ItemAt(index);
372	RemoveItems(index, 1, NULL, false);
373	return item;
374}
375
376
377bool
378BMenu::RemoveItems(int32 index, int32 count, bool del)
379{
380	return RemoveItems(index, count, NULL, del);
381}
382
383
384bool
385BMenu::RemoveItem(BMenu *submenu)
386{
387	for (int i = 0; i < fItems.CountItems(); i++)
388		if (static_cast<BMenuItem *>(fItems.ItemAt(i))->Submenu() == submenu)
389			return RemoveItems(i, 1, NULL, false);
390
391	return false;
392}
393
394
395int32
396BMenu::CountItems() const
397{
398	return fItems.CountItems();
399}
400
401
402BMenuItem *
403BMenu::ItemAt(int32 index) const
404{
405	return static_cast<BMenuItem *>(fItems.ItemAt(index));
406}
407
408
409BMenu *
410BMenu::SubmenuAt(int32 index) const
411{
412	return static_cast<BMenuItem *>(fItems.ItemAt(index))->Submenu();
413}
414
415
416int32
417BMenu::IndexOf(BMenuItem *item) const
418{
419	return fItems.IndexOf(item);
420}
421
422
423int32
424BMenu::IndexOf(BMenu *submenu) const
425{
426	for (int32 i = 0; i < fItems.CountItems(); i++)
427		if (static_cast<BMenuItem *>(fItems.ItemAt(i))->Submenu() == submenu)
428			return i;
429
430	return -1;
431}
432
433
434BMenuItem *
435BMenu::FindItem(const char *label) const
436{
437	BMenuItem *item = NULL;
438
439	for (int32 i = 0; i < CountItems(); i++) {
440		item = ItemAt(i);
441
442		if (item->Label() && strcmp(item->Label(), label) == 0)
443			break;
444
445		if (item->Submenu()) {
446			item = item->Submenu()->FindItem(label);
447			if (item)
448				break;
449		}
450	}
451
452	return item;
453}
454
455
456BMenuItem *
457BMenu::FindItem(uint32 command) const
458{
459	BMenuItem *item = NULL;
460
461	for (int32 i = 0; i < CountItems(); i++) {
462		item = ItemAt(i);
463
464		if (item->Command() == command)
465			break;
466
467		if (item->Submenu()) {
468			item = item->Submenu()->FindItem(command);
469			if (item)
470				break;
471		}
472	}
473
474	return item;
475}
476
477
478status_t
479BMenu::SetTargetForItems(BHandler *handler)
480{
481	for (int32 i = 0; i < fItems.CountItems(); i++)
482		if (static_cast<BMenuItem *>(fItems.ItemAt(i))->SetTarget(handler) != B_OK)
483			return B_ERROR;
484
485	return B_OK;
486}
487
488
489status_t
490BMenu::SetTargetForItems(BMessenger messenger)
491{
492	for (int32 i = 0; i < fItems.CountItems (); i++)
493		if (ItemAt(i)->SetTarget(messenger) != B_OK)
494			return B_ERROR;
495
496	return B_OK;
497}
498
499
500void
501BMenu::SetEnabled(bool enabled)
502{
503	fEnabled = enabled;
504
505	for (int32 i = 0; i < CountItems(); i++)
506		ItemAt(i)->SetEnabled(enabled);
507}
508
509
510void
511BMenu::SetRadioMode(bool flag)
512{
513	fRadioMode = flag;
514}
515
516
517void
518BMenu::SetTriggersEnabled(bool flag)
519{
520	fTriggerEnabled = flag;
521}
522
523
524void
525BMenu::SetMaxContentWidth(float width)
526{
527	fMaxContentWidth = width;
528}
529
530
531void
532BMenu::SetLabelFromMarked(bool flag)
533{
534	fDynamicName = flag;
535}
536
537
538bool
539BMenu::IsLabelFromMarked()
540{
541	return fDynamicName;
542}
543
544
545bool
546BMenu::IsEnabled() const
547{
548	return fEnabled;
549}
550
551
552bool
553BMenu::IsRadioMode() const
554{
555	return fRadioMode;
556}
557
558
559bool
560BMenu::AreTriggersEnabled() const
561{
562	return fTriggerEnabled;
563}
564
565
566bool
567BMenu::IsRedrawAfterSticky() const
568{
569	return fRedrawAfterSticky;
570}
571
572
573float
574BMenu::MaxContentWidth() const
575{
576	return fMaxContentWidth;
577}
578
579
580BMenuItem *
581BMenu::FindMarked()
582{
583	for (int i = 0; i < fItems.CountItems(); i++)
584		if (((BMenuItem*)fItems.ItemAt(i))->IsMarked())
585			return (BMenuItem*)fItems.ItemAt(i);
586
587	return NULL;
588}
589
590
591BMenu *
592BMenu::Supermenu() const
593{
594	return fSuper;
595}
596
597
598BMenuItem *
599BMenu::Superitem() const
600{
601	return fSuperitem;
602}
603
604
605void
606BMenu::MessageReceived(BMessage *msg)
607{
608	BView::MessageReceived(msg);
609}
610
611
612void
613BMenu::KeyDown(const char *bytes, int32 numBytes)
614{
615	switch (bytes[0]) {
616		case B_UP_ARROW:
617		{
618			if (fSelected) 	{
619				fSelected->fSelected = false;
620
621				if (fSelected == fItems.FirstItem())
622					fSelected = static_cast<BMenuItem *>(fItems.LastItem());
623				else
624					fSelected = ItemAt(IndexOf(fSelected) - 1);
625			} else
626				fSelected = static_cast<BMenuItem *>(fItems.LastItem());
627
628			fSelected->fSelected = true;
629
630			break;
631		}
632		case B_DOWN_ARROW:
633		{
634			if (fSelected) {
635				fSelected->fSelected = false;
636
637				if (fSelected == fItems.LastItem())
638					fSelected = static_cast<BMenuItem *>(fItems.FirstItem());
639				else
640					fSelected = ItemAt(IndexOf(fSelected) + 1);
641			} else
642				fSelected = static_cast<BMenuItem *>(fItems.FirstItem());
643
644			fSelected->fSelected = true;
645
646			break;
647		}
648		case B_HOME:
649		{
650			if (fSelected)
651				fSelected->fSelected = false;
652
653			fSelected = static_cast<BMenuItem *>(fItems.FirstItem());
654			fSelected->fSelected = true;
655
656			break;
657		}
658		case B_END:
659		{
660			if (fSelected)
661				fSelected->fSelected = false;
662
663			fSelected = static_cast<BMenuItem *>(fItems.LastItem());
664			fSelected->fSelected = true;
665
666			break;
667		}
668		case B_ENTER:
669		case B_SPACE:
670		{
671			if (fSelected)
672				InvokeItem(fSelected);
673
674			break;
675		}
676		default:
677			BView::KeyDown(bytes, numBytes);
678	}
679}
680
681
682void
683BMenu::Draw(BRect updateRect)
684{
685	DrawBackground(updateRect);
686	DrawItems(updateRect);
687}
688
689
690void
691BMenu::GetPreferredSize(float *width, float *height)
692{
693	ComputeLayout(0, true, false, width, height);
694}
695
696
697void
698BMenu::ResizeToPreferred()
699{
700	BView::ResizeToPreferred();
701}
702
703
704void
705BMenu::FrameMoved(BPoint new_position)
706{
707	BView::FrameMoved(new_position);
708}
709
710
711void
712BMenu::FrameResized(float new_width, float new_height)
713{
714	BView::FrameResized(new_width, new_height);
715}
716
717
718void
719BMenu::InvalidateLayout()
720{
721	CacheFontInfo();
722	LayoutItems(0);
723	Invalidate();
724}
725
726
727BHandler *
728BMenu::ResolveSpecifier(BMessage *msg, int32 index,
729								  BMessage *specifier, int32 form,
730								  const char *property)
731{
732	BPropertyInfo propInfo(sPropList);
733	BHandler *target = NULL;
734
735	switch (propInfo.FindMatch(msg, 0, specifier, form, property)) {
736		case B_ERROR:
737			break;
738
739		case 0:
740		case 1:
741		case 2:
742		case 3:
743		case 4:
744		case 5:
745		case 6:
746		case 7:
747			target = this;
748			break;
749		case 8:
750			// TODO: redirect to menu
751			target = this;
752			break;
753		case 9:
754		case 10:
755		case 11:
756		case 12:
757			target = this;
758			break;
759		case 13:
760			// TODO: redirect to menuitem
761			target = this;
762			break;
763	}
764
765	if (!target)
766		target = BView::ResolveSpecifier(msg, index, specifier, form,
767		property);
768
769	return target;
770}
771
772
773status_t
774BMenu::GetSupportedSuites(BMessage *data)
775{
776	status_t err;
777
778	if (data == NULL)
779		return B_BAD_VALUE;
780
781	err = data->AddString("suites", "suite/vnd.Be-menu");
782
783	if (err < B_OK)
784		return err;
785
786	BPropertyInfo prop_info(sPropList);
787	err = data->AddFlat("messages", &prop_info);
788
789	if (err < B_OK)
790		return err;
791
792	return BView::GetSupportedSuites(data);
793}
794
795
796status_t
797BMenu::Perform(perform_code d, void *arg)
798{
799	return BView::Perform(d, arg);
800}
801
802
803void
804BMenu::MakeFocus(bool focused)
805{
806	BView::MakeFocus(focused);
807}
808
809
810void
811BMenu::AllAttached()
812{
813	BView::AllAttached();
814}
815
816
817void
818BMenu::AllDetached()
819{
820	BView::AllDetached();
821}
822
823
824BMenu::BMenu(BRect frame, const char *name, uint32 resizingMode, uint32 flags,
825			 menu_layout layout, bool resizeToFit)
826	:	BView(frame, name, resizingMode, flags),
827		fChosenItem(NULL),
828		fSelected(NULL),
829		fCachedMenuWindow(NULL),
830		fSuper(NULL),
831		fSuperitem(NULL),
832		fAscent(-1.0f),
833		fDescent(-1.0f),
834		fFontHeight(-1.0f),
835		fState(0),
836		fLayout(layout),
837		fExtraRect(NULL),
838		fMaxContentWidth(0.0f),
839		fInitMatrixSize(NULL),
840		fExtraMenuData(NULL),
841		fTrigger(0),
842		fResizeToFit(resizeToFit),
843		fUseCachedMenuLayout(true),
844		fEnabled(true),
845		fDynamicName(false),
846		fRadioMode(false),
847		fTrackNewBounds(false),
848		fStickyMode(false),
849		fIgnoreHidden(true),
850		fTriggerEnabled(true),
851		fRedrawAfterSticky(false),
852		fAttachAborted(false)
853{
854	InitData(NULL);
855}
856
857
858BPoint
859BMenu::ScreenLocation()
860{
861	BMenu *superMenu = Supermenu();
862	BMenuItem *superItem = Superitem();
863
864	if (superMenu == NULL && superItem == NULL) {
865		debugger("BMenu can't determine where to draw."
866			"Override BMenu::ScreenLocation() to determine location.");
867	}
868
869	BPoint point;
870	if (superMenu->Layout() == B_ITEMS_IN_COLUMN)
871		point = superItem->Frame().RightTop();
872	else
873		point = superItem->Frame().LeftBottom() + BPoint(0.0f, 1.0f);
874
875	superMenu->ConvertToScreen(&point);
876
877	return point;
878}
879
880
881void
882BMenu::SetItemMargins(float left, float top, float right, float bottom)
883{
884	fPad.Set(left, top, right, bottom);
885}
886
887
888void
889BMenu::GetItemMargins(float *left, float *top, float *right,
890						   float *bottom) const
891{
892	if (left != NULL)
893		*left = fPad.left;
894	if (top != NULL)
895		*top = fPad.top;
896	if (right != NULL)
897		*right = fPad.right;
898	if (bottom != NULL)
899		*bottom = fPad.bottom;
900}
901
902
903menu_layout
904BMenu::Layout() const
905{
906	return fLayout;
907}
908
909
910void
911BMenu::Show()
912{
913	Show(false);
914}
915
916
917void
918BMenu::Show(bool selectFirst)
919{
920	_show(selectFirst);
921}
922
923
924void
925BMenu::Hide()
926{
927	_hide();
928}
929
930
931BMenuItem *
932BMenu::Track(bool openAnyway, BRect *clickToOpenRect)
933{
934	if (IsStickyPrefOn())
935		openAnyway = false;
936
937	SetStickyMode(openAnyway);
938
939	if (LockLooper()) {
940		RedrawAfterSticky(Bounds());
941		UnlockLooper();
942	}
943
944	int action;
945	return _track(&action, -1);
946}
947
948
949bool
950BMenu::AddDynamicItem(add_state s)
951{
952	// Implemented in subclasses
953	return false;
954}
955
956
957void
958BMenu::DrawBackground(BRect update)
959{
960	BRect rect = Bounds() & update;
961	rgb_color oldColor = HighColor();
962
963	SetHighColor(sMenuInfo.background_color);
964	FillRect(rect, B_SOLID_HIGH);
965
966	SetHighColor(oldColor);
967}
968
969
970void
971BMenu::SetTrackingHook(menu_tracking_hook func, void *state)
972{
973}
974
975
976void BMenu::_ReservedMenu3() {}
977void BMenu::_ReservedMenu4() {}
978void BMenu::_ReservedMenu5() {}
979void BMenu::_ReservedMenu6() {}
980
981
982BMenu &
983BMenu::operator=(const BMenu &)
984{
985	return *this;
986}
987
988
989void
990BMenu::InitData(BMessage *data)
991{
992	// TODO: Get _color, _fname, _fflt from the message, if present
993	BFont font;
994	font.SetFamilyAndStyle(sMenuInfo.f_family, sMenuInfo.f_style);
995	font.SetSize(sMenuInfo.font_size);
996	SetFont(&font, B_FONT_FAMILY_AND_STYLE | B_FONT_SIZE);
997
998	SetLowColor(sMenuInfo.background_color);
999	SetViewColor(sMenuInfo.background_color);
1000
1001	if (data != NULL) {
1002		data->FindInt32("_layout", (int32 *)&fLayout);
1003		data->FindBool("_rsize_to_fit", &fResizeToFit);
1004		data->FindBool("_disable", &fEnabled);
1005		data->FindBool("_radio", &fRadioMode);
1006
1007		bool disableTrigger = false;
1008		data->FindBool("_trig_disabled", &disableTrigger);
1009		fTriggerEnabled = !disableTrigger;
1010
1011		data->FindBool("_dyn_label", &fDynamicName);
1012		data->FindFloat("_maxwidth", &fMaxContentWidth);
1013	}
1014}
1015
1016
1017bool
1018BMenu::_show(bool selectFirstItem)
1019{
1020	BWindow *window = new BMenuWindow(this);
1021
1022	window->ResizeTo(Bounds().Width() + 1, Bounds().Height() + 1);
1023	window->MoveTo(ScreenLocation());
1024	window->Show();
1025
1026	return true;
1027}
1028
1029
1030void
1031BMenu::_hide()
1032{
1033	BWindow *window = Window();
1034	if (window) {
1035		window->RemoveChild(this);
1036		window->Lock();
1037		window->Quit();
1038	}
1039
1040}
1041
1042
1043BMenuItem *
1044BMenu::_track(int *action, long start)
1045{
1046	// TODO: Take Sticky mode into account, handle submenus
1047	BPoint location;
1048	ulong buttons;
1049	BMenuItem *item = NULL;
1050	do {
1051		if (LockLooper()) {
1052			GetMouse(&location, &buttons);
1053
1054			item = HitTestItems(location, B_ORIGIN);
1055			if (item == NULL) {
1056				UnlockLooper();
1057				break;
1058			}
1059
1060			// TODO: Sometimes the menu flickers a bit.
1061			// try to be smarter and suggest an update area,
1062			// instead of invalidating the whole view.
1063			if (item != fSelected) {
1064				SelectItem(item);
1065				Invalidate();
1066			}
1067
1068			UnlockLooper();
1069		}
1070
1071		snooze(50000);
1072	} while (buttons != 0);
1073
1074	// TODO: A deeper investigation of actions
1075	// would be nice. Consider building an enum
1076	// with the possible actions, and putting it in a
1077	// private, shared header (BMenuBar needs to know about them too).
1078	if (action != NULL) {
1079		if (buttons != 0)
1080			*action = 0;
1081		else
1082			*action = 5;
1083	}
1084
1085	if (LockLooper()) {
1086		SelectItem(NULL);
1087		UnlockLooper();
1088	}
1089
1090	return item;
1091}
1092
1093
1094bool
1095BMenu::_AddItem(BMenuItem *item, int32 index)
1096{
1097	return false;
1098}
1099
1100
1101bool
1102BMenu::RemoveItems(int32 index, int32 count, BMenuItem *_item, bool del)
1103{
1104	bool result = false;
1105
1106	// The plan is simple: If we're given a BMenuItem directly, we use it
1107	// and ignore index and count. Otherwise, we use them instead.
1108	if (_item != NULL) {
1109		// TODO: Check if this is enough.
1110		fItems.RemoveItem(_item);
1111		_item->Uninstall();
1112		if (del)
1113			delete _item;
1114		result = true;
1115	} else {
1116		BMenuItem *item = NULL;
1117		// We iterate backwards because it's simpler
1118		// TODO: We should check if index and count are in bounds.
1119		for (int32 i = index + count - 1; i >= index; i--) {
1120			item = static_cast<BMenuItem *>(fItems.ItemAt(index));
1121			if (item != NULL) {
1122				// TODO: Check if this is enough.
1123				fItems.RemoveItem(item);
1124				item->Uninstall();
1125				if (del)
1126					delete item;
1127				if (!result)
1128					result = true;
1129			}
1130		}
1131	}
1132
1133	InvalidateLayout();
1134
1135	return result;
1136}
1137
1138
1139void
1140BMenu::LayoutItems(int32 index)
1141{
1142	CalcTriggers();
1143
1144	float width, height;
1145	ComputeLayout(index, true, true, &width, &height);
1146
1147	ResizeTo(width, height);
1148
1149	// TODO: Looks like this call is needed when the layout is
1150	// B_ITEMS_IN_MATRIX, otherwise the view is placed in a wrong place
1151	// (by the above call). See if we can avoid this by being
1152	// smarter in other places.
1153	if (fLayout == B_ITEMS_IN_MATRIX)
1154		MoveTo(B_ORIGIN);
1155}
1156
1157
1158void
1159BMenu::ComputeLayout(int32 index, bool bestFit, bool moveItems,
1160						  float* width, float* height)
1161{
1162	BRect frame(0, 0, 0, 0);
1163	float iWidth, iHeight;
1164	BMenuItem *item = NULL;
1165
1166	switch (fLayout) {
1167		case B_ITEMS_IN_COLUMN:
1168		{
1169			for (int32 i = 0; i < fItems.CountItems(); i++) {
1170				item = ItemAt(i);
1171				if (item != NULL) {
1172					item->GetContentSize(&iWidth, &iHeight);
1173
1174					if (item->fModifiers && item->fShortcutChar)
1175						iWidth += 25.0f;
1176
1177					item->fBounds.left = 2.0f;
1178					item->fBounds.top = frame.bottom;
1179					item->fBounds.bottom = item->fBounds.top + iHeight + fPad.top + fPad.bottom;
1180
1181					frame.right = max_c(frame.right, iWidth + fPad.left + fPad.right);
1182					frame.bottom = item->fBounds.bottom + 1.0f;
1183				}
1184			}
1185
1186			for (int32 i = 0; i < fItems.CountItems(); i++)
1187				ItemAt(i)->fBounds.right = frame.right;
1188
1189			frame.right = (float)ceil(frame.right) + 2.0f;
1190			frame.bottom += 1.0f;
1191			break;
1192		}
1193
1194		case B_ITEMS_IN_ROW:
1195		{
1196			font_height fh;
1197			GetFontHeight(&fh);
1198			frame = BRect(0.0f, 0.0f, 0.0f,
1199				(float)ceil(fh.ascent) + (float)ceil(fh.descent) + fPad.top + fPad.bottom);
1200
1201			for (int32 i = 0; i < fItems.CountItems(); i++) {
1202				item = ItemAt(i);
1203				if (item != NULL) {
1204					item->GetContentSize(&iWidth, &iHeight);
1205
1206					item->fBounds.left = frame.right;
1207					item->fBounds.top = 0.0f;
1208					item->fBounds.right = item->fBounds.left + iWidth + fPad.left + fPad.right;
1209
1210					frame.right = item->fBounds.right + 1.0f;
1211					frame.bottom = max_c(frame.bottom, iHeight + fPad.top + fPad.bottom);
1212				}
1213			}
1214
1215			for (int i = 0; i < fItems.CountItems(); i++)
1216				ItemAt(i)->fBounds.bottom = frame.bottom;
1217
1218			frame.right = (float)ceil(frame.right) + 8.0f;
1219			break;
1220		}
1221
1222		case B_ITEMS_IN_MATRIX:
1223		{
1224			for (int32 i = 0; i < CountItems(); i++) {
1225				item = ItemAt(i);
1226				if (item != NULL) {
1227					frame.left = min_c(frame.left, item->Frame().left);
1228					frame.right = max_c(frame.right, item->Frame().right);
1229					frame.top = min_c(frame.top, item->Frame().top);
1230					frame.bottom = max_c(frame.bottom, item->Frame().bottom);
1231				}
1232			}
1233			break;
1234		}
1235
1236		default:
1237			break;
1238	}
1239
1240	if ((ResizingMode() & B_FOLLOW_LEFT_RIGHT) == B_FOLLOW_LEFT_RIGHT) {
1241		if (Parent())
1242			*width = Parent()->Frame().Width() + 1.0f;
1243		else
1244			*width = Window()->Frame().Width() + 1.0f;
1245
1246		*height = frame.Height();
1247	} else {
1248		*width = frame.Width();
1249		*height = frame.Height();
1250	}
1251}
1252
1253
1254BRect
1255BMenu::Bump(BRect current, BPoint extent, int32 index) const
1256{
1257	return BRect();
1258}
1259
1260
1261BPoint
1262BMenu::ItemLocInRect(BRect frame) const
1263{
1264	return BPoint();
1265}
1266
1267
1268BRect
1269BMenu::CalcFrame(BPoint where, bool *scrollOn)
1270{
1271	return BRect();
1272}
1273
1274
1275bool
1276BMenu::ScrollMenu(BRect bounds, BPoint loc, bool *fast)
1277{
1278	return false;
1279}
1280
1281
1282void
1283BMenu::ScrollIntoView(BMenuItem *item)
1284{
1285}
1286
1287
1288void
1289BMenu::DrawItems(BRect updateRect)
1290{
1291	for (int32 i = 0; i < fItems.CountItems(); i++) {
1292		if (ItemAt(i)->Frame().Intersects(updateRect))
1293			ItemAt(i)->Draw();
1294	}
1295}
1296
1297
1298int
1299BMenu::State(BMenuItem **item) const
1300{
1301	return 0;
1302}
1303
1304
1305void
1306BMenu::InvokeItem(BMenuItem *item, bool now)
1307{
1308	if (item->Submenu())
1309		item->Submenu()->Show();
1310	else if (IsRadioMode())
1311		item->SetMarked(true);
1312
1313	item->Invoke();
1314}
1315
1316
1317bool
1318BMenu::OverSuper(BPoint loc)
1319{
1320	ConvertToScreen(&loc);
1321
1322	if (!Supermenu())
1323		return false;
1324
1325	return Supermenu()->Frame().Contains(loc);
1326}
1327
1328
1329bool
1330BMenu::OverSubmenu(BMenuItem *item, BPoint loc)
1331{
1332	// TODO: we assume that loc is in screen coords
1333	if (!item->Submenu())
1334		return false;
1335
1336	return item->Submenu()->Window()->Frame().Contains(loc);
1337}
1338
1339
1340BMenuWindow	*
1341BMenu::MenuWindow()
1342{
1343	return fCachedMenuWindow;
1344}
1345
1346
1347void
1348BMenu::DeleteMenuWindow()
1349{
1350	delete fCachedMenuWindow;
1351	fCachedMenuWindow = NULL;
1352}
1353
1354
1355BMenuItem *
1356BMenu::HitTestItems(BPoint where, BPoint slop) const
1357{
1358	// TODO: Take "slop" into account ?
1359	int32 itemCount = CountItems();
1360	for (int32 i = 0; i < itemCount; i++) {
1361		BMenuItem *item = ItemAt(i);
1362		if (item->Frame().Contains(where))
1363			return item;
1364	}
1365
1366	return NULL;
1367}
1368
1369
1370BRect
1371BMenu::Superbounds() const
1372{
1373	if (fSuper)
1374		return fSuper->Bounds();
1375
1376	return BRect();
1377}
1378
1379
1380void
1381BMenu::CacheFontInfo()
1382{
1383	font_height fh;
1384	GetFontHeight(&fh);
1385	fAscent = fh.ascent;
1386	fDescent = fh.descent;
1387	fFontHeight = (float)ceil(fh.ascent + fh.descent + fh.leading);
1388}
1389
1390
1391void
1392BMenu::ItemMarked(BMenuItem *item)
1393{
1394	if (IsRadioMode()) {
1395		for (int32 i = 0; i < CountItems(); i++)
1396			if (ItemAt(i) != item)
1397				ItemAt(i)->SetMarked(false);
1398	}
1399
1400	if (IsLabelFromMarked() && Superitem())
1401		Superitem()->SetLabel(item->Label());
1402}
1403
1404
1405void
1406BMenu::Install(BWindow *target)
1407{
1408	for (int32 i = 0; i < CountItems(); i++)
1409		ItemAt(i)->Install(target);
1410}
1411
1412
1413void
1414BMenu::Uninstall()
1415{
1416	for (int32 i = 0; i < CountItems(); i++)
1417		ItemAt(i)->Uninstall();
1418}
1419
1420
1421void
1422BMenu::SelectItem(BMenuItem *menuItem, uint32 showSubmenu, bool selectFirstItem)
1423{
1424	// TODO: make use of "showSubmenu" and "selectFirstItem".
1425	if (fSelected != NULL) {
1426		fSelected->Select(false);
1427		if (fSelected->Submenu() != NULL)
1428			fSelected->Submenu()->_hide();
1429	}
1430
1431	if (menuItem != NULL)
1432		menuItem->Select(true);
1433
1434	fSelected = menuItem;
1435	if (fSelected != NULL && fSelected->Submenu() != NULL)
1436		fSelected->Submenu()->_show();
1437
1438}
1439
1440
1441BMenuItem *
1442BMenu::CurrentSelection() const
1443{
1444	return fSelected;
1445}
1446
1447
1448bool
1449BMenu::SelectNextItem(BMenuItem *item, bool forward)
1450{
1451	return false;
1452}
1453
1454
1455BMenuItem *
1456BMenu::NextItem(BMenuItem *item, bool forward) const
1457{
1458	return NULL;
1459}
1460
1461
1462bool
1463BMenu::IsItemVisible(BMenuItem *item) const
1464{
1465	return false;
1466}
1467
1468
1469void
1470BMenu::SetIgnoreHidden(bool on)
1471{
1472	fIgnoreHidden = on;
1473}
1474
1475
1476void
1477BMenu::SetStickyMode(bool on)
1478{
1479	fStickyMode = on;
1480}
1481
1482
1483bool
1484BMenu::IsStickyMode() const
1485{
1486	return fStickyMode;
1487}
1488
1489
1490void
1491BMenu::CalcTriggers()
1492{
1493}
1494
1495
1496const char *
1497BMenu::ChooseTrigger(const char *title, BList *chars)
1498{
1499	return NULL;
1500}
1501
1502
1503void
1504BMenu::UpdateWindowViewSize(bool upWind)
1505{
1506}
1507
1508
1509bool
1510BMenu::IsStickyPrefOn()
1511{
1512	return sMenuInfo.click_to_open;
1513}
1514
1515
1516void
1517BMenu::RedrawAfterSticky(BRect bounds)
1518{
1519}
1520
1521
1522bool
1523BMenu::OkToProceed(BMenuItem *)
1524{
1525	return false;
1526}
1527
1528
1529status_t
1530BMenu::ParseMsg(BMessage *msg, int32 *sindex, BMessage *spec,
1531						 int32 *form, const char **prop, BMenu **tmenu,
1532						 BMenuItem **titem, int32 *user_data,
1533						 BMessage *reply) const
1534{
1535	return B_ERROR;
1536}
1537
1538
1539status_t
1540BMenu::DoMenuMsg(BMenuItem **next, BMenu *tar, BMessage *m,
1541						  BMessage *r, BMessage *spec, int32 f) const
1542{
1543	return B_ERROR;
1544}
1545
1546
1547status_t
1548BMenu::DoMenuItemMsg(BMenuItem **next, BMenu *tar, BMessage *m,
1549							  BMessage *r, BMessage *spec, int32 f) const
1550{
1551	return B_ERROR;
1552}
1553
1554
1555status_t
1556BMenu::DoEnabledMsg(BMenuItem *ti, BMenu *tm, BMessage *m,
1557							 BMessage *r) const
1558{
1559	return B_ERROR;
1560}
1561
1562
1563status_t
1564BMenu::DoLabelMsg(BMenuItem *ti, BMenu *tm, BMessage *m,
1565						   BMessage *r) const
1566{
1567	return B_ERROR;
1568}
1569
1570
1571status_t
1572BMenu::DoMarkMsg(BMenuItem *ti, BMenu *tm, BMessage *m,
1573						  BMessage *r) const
1574{
1575	return B_ERROR;
1576}
1577
1578
1579status_t
1580BMenu::DoDeleteMsg(BMenuItem *ti, BMenu *tm, BMessage *m,
1581							BMessage *r) const
1582{
1583	return B_ERROR;
1584}
1585
1586
1587status_t
1588BMenu::DoCreateMsg(BMenuItem *ti, BMenu *tm, BMessage *m,
1589							BMessage *r, bool menu) const
1590{
1591	return B_ERROR;
1592}
1593
1594
1595status_t
1596set_menu_info(menu_info *info)
1597{
1598	if (!info)
1599		return B_BAD_VALUE;
1600
1601	BPath path;
1602
1603	if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK)
1604		return B_OK;
1605
1606	path.Append("menu_settings");
1607
1608	BFile file(path.Path(), B_WRITE_ONLY | B_CREATE_FILE);
1609
1610	if (file.InitCheck() != B_OK)
1611		return B_OK;
1612
1613	file.Write(info, sizeof(menu_info));
1614
1615	BMenu::sMenuInfo = *info;
1616
1617	return B_OK;
1618}
1619
1620
1621status_t
1622get_menu_info(menu_info *info)
1623{
1624	if (!info)
1625		return B_BAD_VALUE;
1626
1627	*info = BMenu::sMenuInfo;
1628
1629	return B_OK;
1630}
1631