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