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