xref: /haiku/src/kits/interface/Menu.cpp (revision adb0d19d561947362090081e81d90dde59142026)
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 
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 		}
2049 
2050 		item->fBounds.left = 0.0f;
2051 		item->fBounds.top = frame.bottom + 1.0f;
2052 		item->fBounds.bottom = item->fBounds.top + height + fPad.top
2053 			+ fPad.bottom;
2054 
2055 		if (item->fSubmenu != NULL)
2056 			width += item->Frame().Height();
2057 
2058 		frame.right = max_c(frame.right, width + fPad.left + fPad.right);
2059 		frame.bottom = item->fBounds.bottom;
2060 	}
2061 
2062 	if (command)
2063 		frame.right += 17;
2064 	if (control)
2065 		frame.right += 17;
2066 	if (shift)
2067 		frame.right += 22;
2068 
2069 	if (fMaxContentWidth > 0)
2070 		frame.right = min_c(frame.right, fMaxContentWidth);
2071 
2072 	if (moveItems) {
2073 		for (int32 i = 0; i < fItems.CountItems(); i++)
2074 			ItemAt(i)->fBounds.right = frame.right;
2075 	}
2076 
2077 	frame.top = 0;
2078 	frame.right = ceilf(frame.right);
2079 }
2080 
2081 
2082 void
2083 BMenu::_ComputeRowLayout(int32 index, bool bestFit, bool moveItems,
2084 	BRect& frame)
2085 {
2086 	font_height fh;
2087 	GetFontHeight(&fh);
2088 	frame.Set(0.0f, 0.0f, 0.0f, ceilf(fh.ascent + fh.descent + fPad.top
2089 		+ fPad.bottom));
2090 
2091 	for (int32 i = 0; i < fItems.CountItems(); i++) {
2092 		BMenuItem *item = ItemAt(i);
2093 
2094 		float width, height;
2095 		item->GetContentSize(&width, &height);
2096 
2097 		item->fBounds.left = frame.right;
2098 		item->fBounds.top = 0.0f;
2099 		item->fBounds.right = item->fBounds.left + width + fPad.left
2100 			+ fPad.right;
2101 
2102 		frame.right = item->Frame().right + 1.0f;
2103 		frame.bottom = max_c(frame.bottom, height + fPad.top + fPad.bottom);
2104 	}
2105 
2106 	if (moveItems) {
2107 		for (int32 i = 0; i < fItems.CountItems(); i++)
2108 			ItemAt(i)->fBounds.bottom = frame.bottom;
2109 	}
2110 
2111 	if (bestFit)
2112 		frame.right = ceilf(frame.right);
2113 	else
2114 		frame.right = Bounds().right;
2115 }
2116 
2117 
2118 void
2119 BMenu::_ComputeMatrixLayout(BRect &frame)
2120 {
2121 	frame.Set(0, 0, 0, 0);
2122 	for (int32 i = 0; i < CountItems(); i++) {
2123 		BMenuItem *item = ItemAt(i);
2124 		if (item != NULL) {
2125 			frame.left = min_c(frame.left, item->Frame().left);
2126 			frame.right = max_c(frame.right, item->Frame().right);
2127 			frame.top = min_c(frame.top, item->Frame().top);
2128 			frame.bottom = max_c(frame.bottom, item->Frame().bottom);
2129 		}
2130 	}
2131 }
2132 
2133 
2134 // Assumes the SuperMenu to be locked (due to calling ConvertToScreen())
2135 BPoint
2136 BMenu::ScreenLocation()
2137 {
2138 	BMenu *superMenu = Supermenu();
2139 	BMenuItem *superItem = Superitem();
2140 
2141 	if (superMenu == NULL || superItem == NULL) {
2142 		debugger("BMenu can't determine where to draw."
2143 			"Override BMenu::ScreenLocation() to determine location.");
2144 	}
2145 
2146 	BPoint point;
2147 	if (superMenu->Layout() == B_ITEMS_IN_COLUMN)
2148 		point = superItem->Frame().RightTop() + BPoint(1.0f, 1.0f);
2149 	else
2150 		point = superItem->Frame().LeftBottom() + BPoint(1.0f, 1.0f);
2151 
2152 	superMenu->ConvertToScreen(&point);
2153 
2154 	return point;
2155 }
2156 
2157 
2158 BRect
2159 BMenu::_CalcFrame(BPoint where, bool *scrollOn)
2160 {
2161 	// TODO: Improve me
2162 	BRect bounds = Bounds();
2163 	BRect frame = bounds.OffsetToCopy(where);
2164 
2165 	BScreen screen(Window());
2166 	BRect screenFrame = screen.Frame();
2167 
2168 	BMenu *superMenu = Supermenu();
2169 	BMenuItem *superItem = Superitem();
2170 
2171 	bool scroll = false;
2172 	// TODO: Horrible hack:
2173 	// When added to a BMenuField, a BPopUpMenu is the child of
2174 	// a _BMCMenuBar_ to "fake" the menu hierarchy
2175 	if (superMenu == NULL || superItem == NULL
2176 		|| dynamic_cast<_BMCMenuBar_ *>(superMenu) != NULL) {
2177 		// just move the window on screen
2178 
2179 		if (frame.bottom > screenFrame.bottom)
2180 			frame.OffsetBy(0, screenFrame.bottom - frame.bottom);
2181 		else if (frame.top < screenFrame.top)
2182 			frame.OffsetBy(0, -frame.top);
2183 
2184 		if (frame.right > screenFrame.right)
2185 			frame.OffsetBy(screenFrame.right - frame.right, 0);
2186 		else if (frame.left < screenFrame.left)
2187 			frame.OffsetBy(-frame.left, 0);
2188 
2189 	} else if (superMenu->Layout() == B_ITEMS_IN_COLUMN) {
2190 		if (frame.right > screenFrame.right)
2191 			frame.OffsetBy(-superItem->Frame().Width() - frame.Width() - 2, 0);
2192 
2193 		if (frame.left < 0)
2194 			frame.OffsetBy(-frame.left + 6, 0);
2195 
2196 		if (frame.bottom > screenFrame.bottom)
2197 			frame.OffsetBy(0, screenFrame.bottom - frame.bottom);
2198 	} else {
2199 		if (frame.bottom > screenFrame.bottom) {
2200 			if (scrollOn != NULL && superMenu != NULL
2201 				&& dynamic_cast<BMenuBar *>(superMenu) != NULL
2202 				&& frame.top < (screenFrame.bottom - 80)) {
2203 				scroll = true;
2204 			} else {
2205 				frame.OffsetBy(0, -superItem->Frame().Height() - frame.Height() - 3);
2206 			}
2207 		}
2208 
2209 		if (frame.right > screenFrame.right)
2210 			frame.OffsetBy(screenFrame.right - frame.right, 0);
2211 	}
2212 
2213 	if (!scroll) {
2214 		// basically, if this returns false, it means
2215 		// that the menu frame won't fit completely inside the screen
2216 		// TODO: Scrolling, will currently only work up/down,
2217 		// not left/right
2218 		scroll = screenFrame.Height() < frame.Height();
2219 	}
2220 
2221 	if (scrollOn != NULL)
2222 		*scrollOn = scroll;
2223 
2224 	return frame;
2225 }
2226 
2227 
2228 void
2229 BMenu::_DrawItems(BRect updateRect)
2230 {
2231 	int32 itemCount = fItems.CountItems();
2232 	for (int32 i = 0; i < itemCount; i++) {
2233 		BMenuItem *item = ItemAt(i);
2234 		if (item->Frame().Intersects(updateRect))
2235 			item->Draw();
2236 	}
2237 }
2238 
2239 
2240 int
2241 BMenu::_State(BMenuItem **item) const
2242 {
2243 	if (fState == MENU_STATE_TRACKING || fState == MENU_STATE_CLOSED)
2244 		return fState;
2245 
2246 	if (fSelected != NULL && fSelected->Submenu() != NULL)
2247 		return fSelected->Submenu()->_State(item);
2248 
2249 	return fState;
2250 }
2251 
2252 
2253 void
2254 BMenu::_InvokeItem(BMenuItem *item, bool now)
2255 {
2256 	if (!item->IsEnabled())
2257 		return;
2258 
2259 	// Do the "selected" animation
2260 	// TODO: Doesn't work. This is supposed to highlight
2261 	// and dehighlight the item, works on beos but not on haiku.
2262 	if (!item->Submenu() && LockLooper()) {
2263 		snooze(50000);
2264 		item->Select(true);
2265 		Sync();
2266 		snooze(50000);
2267 		item->Select(false);
2268 		Sync();
2269 		snooze(50000);
2270 		item->Select(true);
2271 		Sync();
2272 		snooze(50000);
2273 		item->Select(false);
2274 		Sync();
2275 		UnlockLooper();
2276 	}
2277 
2278 	item->Invoke();
2279 }
2280 
2281 
2282 bool
2283 BMenu::_OverSuper(BPoint location)
2284 {
2285 	if (!Supermenu())
2286 		return false;
2287 
2288 	return fSuperbounds.Contains(location);
2289 }
2290 
2291 
2292 bool
2293 BMenu::_OverSubmenu(BMenuItem *item, BPoint loc)
2294 {
2295 	if (item == NULL)
2296 		return false;
2297 
2298 	BMenu *subMenu = item->Submenu();
2299 	if (subMenu == NULL || subMenu->Window() == NULL)
2300 		return false;
2301 
2302 	// we assume that loc is in screen coords {
2303 	if (subMenu->Window()->Frame().Contains(loc))
2304 		return true;
2305 
2306 	return subMenu->_OverSubmenu(subMenu->fSelected, loc);
2307 }
2308 
2309 
2310 BMenuWindow *
2311 BMenu::_MenuWindow()
2312 {
2313 #if USE_CACHED_MENUWINDOW
2314 	if (fCachedMenuWindow == NULL) {
2315 		char windowName[64];
2316 		snprintf(windowName, 64, "%s cached menu", Name());
2317 		fCachedMenuWindow = new (nothrow) BMenuWindow(windowName);
2318 	}
2319 #endif
2320 	return fCachedMenuWindow;
2321 }
2322 
2323 
2324 void
2325 BMenu::_DeleteMenuWindow()
2326 {
2327 	if (fCachedMenuWindow != NULL) {
2328 		fCachedMenuWindow->Lock();
2329 		fCachedMenuWindow->Quit();
2330 		fCachedMenuWindow = NULL;
2331 	}
2332 }
2333 
2334 
2335 BMenuItem *
2336 BMenu::_HitTestItems(BPoint where, BPoint slop) const
2337 {
2338 	// TODO: Take "slop" into account ?
2339 
2340 	// if the point doesn't lie within the menu's
2341 	// bounds, bail out immediately
2342 	if (!Bounds().Contains(where))
2343 		return NULL;
2344 
2345 	int32 itemCount = CountItems();
2346 	for (int32 i = 0; i < itemCount; i++) {
2347 		BMenuItem *item = ItemAt(i);
2348 		if (item->Frame().Contains(where))
2349 			return item;
2350 	}
2351 
2352 	return NULL;
2353 }
2354 
2355 
2356 BRect
2357 BMenu::_Superbounds() const
2358 {
2359 	return fSuperbounds;
2360 }
2361 
2362 
2363 void
2364 BMenu::_CacheFontInfo()
2365 {
2366 	font_height fh;
2367 	GetFontHeight(&fh);
2368 	fAscent = fh.ascent;
2369 	fDescent = fh.descent;
2370 	fFontHeight = ceilf(fh.ascent + fh.descent + fh.leading);
2371 }
2372 
2373 
2374 void
2375 BMenu::_ItemMarked(BMenuItem *item)
2376 {
2377 	if (IsRadioMode()) {
2378 		for (int32 i = 0; i < CountItems(); i++) {
2379 			if (ItemAt(i) != item)
2380 				ItemAt(i)->SetMarked(false);
2381 		}
2382 		InvalidateLayout();
2383 	}
2384 
2385 	if (IsLabelFromMarked() && Superitem())
2386 		Superitem()->SetLabel(item->Label());
2387 }
2388 
2389 
2390 void
2391 BMenu::_Install(BWindow *target)
2392 {
2393 	for (int32 i = 0; i < CountItems(); i++)
2394 		ItemAt(i)->Install(target);
2395 }
2396 
2397 
2398 void
2399 BMenu::_Uninstall()
2400 {
2401 	for (int32 i = 0; i < CountItems(); i++)
2402 		ItemAt(i)->Uninstall();
2403 }
2404 
2405 
2406 void
2407 BMenu::_SelectItem(BMenuItem* menuItem, bool showSubmenu, bool selectFirstItem)
2408 {
2409 	// Avoid deselecting and then reselecting the same item
2410 	// which would cause flickering
2411 	if (menuItem != fSelected) {
2412 		if (fSelected != NULL) {
2413 			fSelected->Select(false);
2414 			BMenu *subMenu = fSelected->Submenu();
2415 			if (subMenu != NULL && subMenu->Window() != NULL)
2416 				subMenu->_Hide();
2417 		}
2418 
2419 		fSelected = menuItem;
2420 		if (fSelected != NULL)
2421 			fSelected->Select(true);
2422 	}
2423 
2424 	if (fSelected != NULL && showSubmenu) {
2425 		BMenu *subMenu = fSelected->Submenu();
2426 		if (subMenu != NULL && subMenu->Window() == NULL) {
2427 			if (!subMenu->_Show(selectFirstItem)) {
2428 				// something went wrong, deselect the item
2429 				fSelected->Select(false);
2430 				fSelected = NULL;
2431 			}
2432 		}
2433 	}
2434 }
2435 
2436 
2437 bool
2438 BMenu::_SelectNextItem(BMenuItem *item, bool forward)
2439 {
2440 	if (CountItems() == 0) // cannot select next item in an empty menu
2441 		return false;
2442 
2443 	BMenuItem *nextItem = _NextItem(item, forward);
2444 	if (nextItem == NULL)
2445 		return false;
2446 
2447 	bool openMenu = false;
2448 	if (dynamic_cast<BMenuBar *>(this) != NULL)
2449 		openMenu = true;
2450 	_SelectItem(nextItem, openMenu);
2451 	return true;
2452 }
2453 
2454 
2455 BMenuItem *
2456 BMenu::_NextItem(BMenuItem *item, bool forward) const
2457 {
2458 	// go to next item, and skip over disabled items such as separators
2459 	int32 index = fItems.IndexOf(item);
2460 	const int32 numItems = fItems.CountItems();
2461 	if (index < 0) {
2462 		if (forward)
2463 			index = -1;
2464 		else
2465 			index = numItems;
2466 	}
2467 	int32 startIndex = index;
2468 	do {
2469 		if (forward)
2470 			index++;
2471 		else
2472 			index--;
2473 
2474 		// cycle through menu items
2475 		if (index < 0)
2476 			index = numItems - 1;
2477 		else if (index >= numItems)
2478 			index = 0;
2479 	} while (!ItemAt(index)->IsEnabled() && index != startIndex);
2480 
2481 	if (index == startIndex) // we are back where we started and no item was enabled
2482 		return NULL;
2483 
2484 	return ItemAt(index);
2485 }
2486 
2487 
2488 void
2489 BMenu::_SetIgnoreHidden(bool on)
2490 {
2491 	fIgnoreHidden = on;
2492 }
2493 
2494 
2495 void
2496 BMenu::_SetStickyMode(bool on)
2497 {
2498 	if (fStickyMode == on)
2499 		return;
2500 
2501 	fStickyMode = on;
2502 
2503 	// If we are switching to sticky mode, propagate the status
2504 	// back to the super menu
2505 	if (fSuper != NULL)
2506 		fSuper->_SetStickyMode(on);
2507 	else {
2508 		// TODO: Ugly hack, but it needs to be done right here in this method
2509 		BMenuBar *menuBar = dynamic_cast<BMenuBar *>(this);
2510 		if (on && menuBar != NULL && menuBar->LockLooper()) {
2511 			// Steal the focus from the current focus view
2512 			// (needed to handle keyboard navigation)
2513 			menuBar->_StealFocus();
2514 			menuBar->UnlockLooper();
2515 		}
2516 	}
2517 }
2518 
2519 
2520 bool
2521 BMenu::_IsStickyMode() const
2522 {
2523 	return fStickyMode;
2524 }
2525 
2526 
2527 void
2528 BMenu::_CalcTriggers()
2529 {
2530 	BPrivate::TriggerList triggerList;
2531 
2532 	// Gathers the existing triggers set by the user
2533 	for (int32 i = 0; i < CountItems(); i++) {
2534 		char trigger = ItemAt(i)->Trigger();
2535 		if (trigger != 0)
2536 			triggerList.AddTrigger(trigger);
2537 	}
2538 
2539 	// Set triggers for items which don't have one yet
2540 	for (int32 i = 0; i < CountItems(); i++) {
2541 		BMenuItem *item = ItemAt(i);
2542 		if (item->Trigger() == 0) {
2543 			uint32 trigger;
2544 			int32 index;
2545 			if (_ChooseTrigger(item->Label(), index, trigger, triggerList))
2546 				item->SetAutomaticTrigger(index, trigger);
2547 		}
2548 	}
2549 }
2550 
2551 
2552 bool
2553 BMenu::_ChooseTrigger(const char *title, int32& index, uint32& trigger,
2554 	BPrivate::TriggerList& triggers)
2555 {
2556 	if (title == NULL)
2557 		return false;
2558 
2559 	uint32 c;
2560 
2561 	// two runs: first we look out for uppercase letters
2562 	// TODO: support Unicode characters correctly!
2563 	for (uint32 i = 0; (c = title[i]) != '\0'; i++) {
2564 		if (!IsInsideGlyph(c) && isupper(c) && !triggers.HasTrigger(c)) {
2565 			index = i;
2566 			trigger = tolower(c);
2567 			return triggers.AddTrigger(c);
2568 		}
2569 	}
2570 
2571 	// then, if we still haven't found anything, we accept them all
2572 	index = 0;
2573 	while ((c = UTF8ToCharCode(&title)) != 0) {
2574 		if (!isspace(c) && !triggers.HasTrigger(c)) {
2575 			trigger = tolower(c);
2576 			return triggers.AddTrigger(c);
2577 		}
2578 
2579 		index++;
2580 	}
2581 
2582 	return false;
2583 }
2584 
2585 
2586 void
2587 BMenu::_UpdateWindowViewSize(bool updatePosition)
2588 {
2589 	BMenuWindow *window = static_cast<BMenuWindow *>(Window());
2590 	if (window == NULL)
2591 		return;
2592 
2593 	if (dynamic_cast<BMenuBar *>(this) != NULL)
2594 		return;
2595 
2596 	if (!fResizeToFit)
2597 		return;
2598 
2599 	bool scroll = false;
2600 	const BPoint screenLocation = updatePosition ? ScreenLocation()
2601 		: window->Frame().LeftTop();
2602 	BRect frame = _CalcFrame(screenLocation, &scroll);
2603 	ResizeTo(frame.Width(), frame.Height());
2604 
2605 	if (fItems.CountItems() > 0) {
2606 		if (!scroll) {
2607 			window->ResizeTo(Bounds().Width(), Bounds().Height());
2608 		} else {
2609 			BScreen screen(window);
2610 
2611 			// If we need scrolling, resize the window to fit the screen and
2612 			// attach scrollers to our cached BMenuWindow.
2613 			if (dynamic_cast<BMenuBar *>(Supermenu()) == NULL) {
2614 				window->ResizeTo(Bounds().Width(), screen.Frame().bottom);
2615 				frame.top = 0;
2616 			} else {
2617 				// Or, in case our parent was a BMenuBar enable scrolling with
2618 				// normal size.
2619 				window->ResizeTo(Bounds().Width(), screen.Frame().bottom
2620 					- frame.top);
2621 			}
2622 
2623 			window->AttachScrollers();
2624 		}
2625 	} else {
2626 		_CacheFontInfo();
2627 		window->ResizeTo(StringWidth(kEmptyMenuLabel) + fPad.left + fPad.right,
2628 			fFontHeight + fPad.top + fPad.bottom);
2629 	}
2630 
2631 	if (updatePosition)
2632 		window->MoveTo(frame.LeftTop());
2633 }
2634 
2635 
2636 bool
2637 BMenu::_OkToProceed(BMenuItem* item)
2638 {
2639 	BPoint where;
2640 	ulong buttons;
2641 	GetMouse(&where, &buttons, false);
2642 	bool stickyMode = _IsStickyMode();
2643 	// Quit if user clicks the mouse button in sticky mode
2644 	// or releases the mouse button in nonsticky mode
2645 	// or moves the pointer over another item
2646 	// TODO: I added the check for BMenuBar to solve a problem with Deskbar.
2647 	// BeOS seems to do something similar. This could also be a bug in Deskbar, though.
2648 	if ((buttons != 0 && stickyMode)
2649 		|| ((dynamic_cast<BMenuBar *>(this) == NULL
2650 			&& (buttons == 0 && !stickyMode)) || _HitTestItems(where) != item))
2651 		return false;
2652 
2653 	return true;
2654 }
2655 
2656 
2657 bool
2658 BMenu::_CustomTrackingWantsToQuit()
2659 {
2660 	if (fExtraMenuData != NULL && fExtraMenuData->trackingHook != NULL
2661 		&& fExtraMenuData->trackingState != NULL) {
2662 		return fExtraMenuData->trackingHook(this, fExtraMenuData->trackingState);
2663 	}
2664 
2665 	return false;
2666 }
2667 
2668 
2669 void
2670 BMenu::_QuitTracking(bool onlyThis)
2671 {
2672 	_SelectItem(NULL);
2673 	if (BMenuBar *menuBar = dynamic_cast<BMenuBar *>(this))
2674 		menuBar->_RestoreFocus();
2675 
2676 	fChosenItem = NULL;
2677 	fState = MENU_STATE_CLOSED;
2678 
2679 	// Close the whole menu hierarchy
2680 	if (!onlyThis && _IsStickyMode())
2681 		_SetStickyMode(false);
2682 
2683 	_Hide();
2684 }
2685 
2686 
2687 //	#pragma mark -
2688 
2689 
2690 // TODO: Maybe the following two methods would fit better into InterfaceDefs.cpp
2691 // In R5, they do all the work client side, we let the app_server handle the details.
2692 status_t
2693 set_menu_info(menu_info *info)
2694 {
2695 	if (!info)
2696 		return B_BAD_VALUE;
2697 
2698 	BPrivate::AppServerLink link;
2699 	link.StartMessage(AS_SET_MENU_INFO);
2700 	link.Attach<menu_info>(*info);
2701 
2702 	status_t status = B_ERROR;
2703 	if (link.FlushWithReply(status) == B_OK && status == B_OK)
2704 		BMenu::sMenuInfo = *info;
2705 		// Update also the local copy, in case anyone relies on it
2706 
2707 	return status;
2708 }
2709 
2710 
2711 status_t
2712 get_menu_info(menu_info *info)
2713 {
2714 	if (!info)
2715 		return B_BAD_VALUE;
2716 
2717 	BPrivate::AppServerLink link;
2718 	link.StartMessage(AS_GET_MENU_INFO);
2719 
2720 	status_t status = B_ERROR;
2721 	if (link.FlushWithReply(status) == B_OK && status == B_OK)
2722 		link.Read<menu_info>(info);
2723 
2724 	return status;
2725 }
2726 
2727 
2728 // MenuPrivate
2729 namespace BPrivate {
2730 
2731 MenuPrivate::MenuPrivate(BMenu *menu)
2732 	:
2733 	fMenu(menu)
2734 {
2735 }
2736 
2737 
2738 menu_layout
2739 MenuPrivate::Layout() const
2740 {
2741 	return fMenu->Layout();
2742 }
2743 
2744 
2745 void
2746 MenuPrivate::ItemMarked(BMenuItem *item)
2747 {
2748 	fMenu->_ItemMarked(item);
2749 }
2750 
2751 
2752 void
2753 MenuPrivate::CacheFontInfo()
2754 {
2755 	fMenu->_CacheFontInfo();
2756 }
2757 
2758 
2759 float
2760 MenuPrivate::FontHeight() const
2761 {
2762 	return fMenu->fFontHeight;
2763 }
2764 
2765 
2766 float
2767 MenuPrivate::Ascent() const
2768 {
2769 	return fMenu->fAscent;
2770 }
2771 
2772 
2773 BRect
2774 MenuPrivate::Padding() const
2775 {
2776 	return fMenu->fPad;
2777 }
2778 
2779 
2780 void
2781 MenuPrivate::GetItemMargins(float *left, float *top,
2782 					float *right, float *bottom) const
2783 {
2784 	fMenu->GetItemMargins(left, top, right, bottom);
2785 }
2786 
2787 
2788 bool
2789 MenuPrivate::IsAltCommandKey() const
2790 {
2791 	return fMenu->sAltAsCommandKey;
2792 }
2793 
2794 
2795 int
2796 MenuPrivate::State(BMenuItem **item) const
2797 {
2798 	return fMenu->_State(item);
2799 }
2800 
2801 
2802 void
2803 MenuPrivate::Install(BWindow *window)
2804 {
2805 	fMenu->_Install(window);
2806 }
2807 
2808 
2809 void
2810 MenuPrivate::Uninstall()
2811 {
2812 	fMenu->_Uninstall();
2813 }
2814 
2815 
2816 void
2817 MenuPrivate::SetSuper(BMenu *menu)
2818 {
2819 	fMenu->fSuper = menu;
2820 }
2821 
2822 
2823 void
2824 MenuPrivate::SetSuperItem(BMenuItem *item)
2825 {
2826 	fMenu->fSuperitem = item;
2827 }
2828 
2829 
2830 void
2831 MenuPrivate::InvokeItem(BMenuItem *item, bool now)
2832 {
2833 	fMenu->_InvokeItem(item, now);
2834 }
2835 
2836 
2837 void
2838 MenuPrivate::QuitTracking(bool thisMenuOnly)
2839 {
2840 	fMenu->_QuitTracking(thisMenuOnly);
2841 }
2842 
2843 }	// namespace BPrivate
2844