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