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