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