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