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