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