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