xref: /haiku/src/kits/interface/Menu.cpp (revision 3216a856947f9746d8c4c1e720ccf3dc5c0ac786)
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 		_UpdateWindowViewSize(false);
2418 		return true;
2419 	}
2420 	return false;
2421 }
2422 
2423 
2424 void
2425 BMenu::_LayoutItems(int32 index)
2426 {
2427 	_CalcTriggers();
2428 
2429 	float width;
2430 	float height;
2431 	_ComputeLayout(index, fResizeToFit, true, &width, &height);
2432 
2433 	if (fResizeToFit)
2434 		ResizeTo(width, height);
2435 }
2436 
2437 
2438 BSize
2439 BMenu::_ValidatePreferredSize()
2440 {
2441 	if (!fLayoutData->preferred.IsWidthSet() || ResizingMode()
2442 			!= fLayoutData->lastResizingMode) {
2443 		_ComputeLayout(0, true, false, NULL, NULL);
2444 		ResetLayoutInvalidation();
2445 	}
2446 
2447 	return fLayoutData->preferred;
2448 }
2449 
2450 
2451 void
2452 BMenu::_ComputeLayout(int32 index, bool bestFit, bool moveItems,
2453 	float* _width, float* _height)
2454 {
2455 	// TODO: Take "bestFit", "moveItems", "index" into account,
2456 	// Recalculate only the needed items,
2457 	// not the whole layout every time
2458 
2459 	fLayoutData->lastResizingMode = ResizingMode();
2460 
2461 	BRect frame;
2462 	switch (fLayout) {
2463 		case B_ITEMS_IN_COLUMN:
2464 		{
2465 			BRect parentFrame;
2466 			BRect* overrideFrame = NULL;
2467 			if (dynamic_cast<_BMCMenuBar_*>(Supermenu()) != NULL) {
2468 				// When the menu is modified while it's open, we get here in a
2469 				// situation where trying to lock the looper would deadlock
2470 				// (the window is locked waiting for the menu to terminate).
2471 				// In that case, just give up on getting the supermenu bounds
2472 				// and keep the menu at the current width and position.
2473 				if (Supermenu()->LockLooperWithTimeout(0) == B_OK) {
2474 					parentFrame = Supermenu()->Bounds();
2475 					Supermenu()->UnlockLooper();
2476 					overrideFrame = &parentFrame;
2477 				}
2478 			}
2479 
2480 			_ComputeColumnLayout(index, bestFit, moveItems, overrideFrame,
2481 				frame);
2482 			break;
2483 		}
2484 
2485 		case B_ITEMS_IN_ROW:
2486 			_ComputeRowLayout(index, bestFit, moveItems, frame);
2487 			break;
2488 
2489 		case B_ITEMS_IN_MATRIX:
2490 			_ComputeMatrixLayout(frame);
2491 			break;
2492 	}
2493 
2494 	// change width depending on resize mode
2495 	BSize size;
2496 	if ((ResizingMode() & B_FOLLOW_LEFT_RIGHT) == B_FOLLOW_LEFT_RIGHT) {
2497 		if (dynamic_cast<_BMCMenuBar_*>(this) != NULL)
2498 			size.width = Bounds().Width() - fPad.right;
2499 		else if (Parent() != NULL)
2500 			size.width = Parent()->Frame().Width();
2501 		else if (Window() != NULL)
2502 			size.width = Window()->Frame().Width();
2503 		else
2504 			size.width = Bounds().Width();
2505 	} else
2506 		size.width = frame.Width();
2507 
2508 	size.height = frame.Height();
2509 
2510 	if (_width)
2511 		*_width = size.width;
2512 
2513 	if (_height)
2514 		*_height = size.height;
2515 
2516 	if (bestFit)
2517 		fLayoutData->preferred = size;
2518 
2519 	if (moveItems)
2520 		fUseCachedMenuLayout = true;
2521 }
2522 
2523 
2524 void
2525 BMenu::_ComputeColumnLayout(int32 index, bool bestFit, bool moveItems,
2526 	BRect* overrideFrame, BRect& frame)
2527 {
2528 	bool command = false;
2529 	bool control = false;
2530 	bool shift = false;
2531 	bool option = false;
2532 	bool submenu = false;
2533 
2534 	if (index > 0)
2535 		frame = ItemAt(index - 1)->Frame();
2536 	else if (overrideFrame != NULL)
2537 		frame.Set(0, 0, overrideFrame->right, -1);
2538 	else
2539 		frame.Set(0, 0, 0, -1);
2540 
2541 	BFont font;
2542 	GetFont(&font);
2543 
2544 	// Loop over all items to set their top, bottom and left coordinates,
2545 	// all while computing the width of the menu
2546 	for (; index < fItems.CountItems(); index++) {
2547 		BMenuItem* item = ItemAt(index);
2548 
2549 		float width;
2550 		float height;
2551 		item->GetContentSize(&width, &height);
2552 
2553 		if (item->fModifiers && item->fShortcutChar) {
2554 			width += font.Size();
2555 			if ((item->fModifiers & B_COMMAND_KEY) != 0)
2556 				command = true;
2557 
2558 			if ((item->fModifiers & B_CONTROL_KEY) != 0)
2559 				control = true;
2560 
2561 			if ((item->fModifiers & B_SHIFT_KEY) != 0)
2562 				shift = true;
2563 
2564 			if ((item->fModifiers & B_OPTION_KEY) != 0)
2565 				option = true;
2566 		}
2567 
2568 		item->fBounds.left = 0.0f;
2569 		item->fBounds.top = frame.bottom + 1.0f;
2570 		item->fBounds.bottom = item->fBounds.top + height + fPad.top
2571 			+ fPad.bottom;
2572 
2573 		if (item->fSubmenu != NULL)
2574 			submenu = true;
2575 
2576 		frame.right = std::max(frame.right, width + fPad.left + fPad.right);
2577 		frame.bottom = item->fBounds.bottom;
2578 	}
2579 
2580 	// Compute the extra space needed for shortcuts and submenus
2581 	if (command) {
2582 		frame.right
2583 			+= BPrivate::MenuPrivate::MenuItemCommand()->Bounds().Width() + 1;
2584 	}
2585 	if (control) {
2586 		frame.right
2587 			+= BPrivate::MenuPrivate::MenuItemControl()->Bounds().Width() + 1;
2588 	}
2589 	if (option) {
2590 		frame.right
2591 			+= BPrivate::MenuPrivate::MenuItemOption()->Bounds().Width() + 1;
2592 	}
2593 	if (shift) {
2594 		frame.right
2595 			+= BPrivate::MenuPrivate::MenuItemShift()->Bounds().Width() + 1;
2596 	}
2597 	if (submenu) {
2598 		frame.right += ItemAt(0)->Frame().Height() / 2;
2599 		fHasSubmenus = true;
2600 	} else {
2601 		fHasSubmenus = false;
2602 	}
2603 
2604 	if (fMaxContentWidth > 0)
2605 		frame.right = std::min(frame.right, fMaxContentWidth);
2606 
2607 	// Finally update the "right" coordinate of all items
2608 	if (moveItems) {
2609 		for (int32 i = 0; i < fItems.CountItems(); i++)
2610 			ItemAt(i)->fBounds.right = frame.right;
2611 	}
2612 
2613 	frame.top = 0;
2614 	frame.right = ceilf(frame.right);
2615 }
2616 
2617 
2618 void
2619 BMenu::_ComputeRowLayout(int32 index, bool bestFit, bool moveItems,
2620 	BRect& frame)
2621 {
2622 	font_height fh;
2623 	GetFontHeight(&fh);
2624 	frame.Set(0.0f, 0.0f, 0.0f, ceilf(fh.ascent + fh.descent + fPad.top
2625 		+ fPad.bottom));
2626 
2627 	for (int32 i = 0; i < fItems.CountItems(); i++) {
2628 		BMenuItem* item = ItemAt(i);
2629 
2630 		float width, height;
2631 		item->GetContentSize(&width, &height);
2632 
2633 		item->fBounds.left = frame.right;
2634 		item->fBounds.top = 0.0f;
2635 		item->fBounds.right = item->fBounds.left + width + fPad.left
2636 			+ fPad.right;
2637 
2638 		frame.right = item->Frame().right + 1.0f;
2639 		frame.bottom = std::max(frame.bottom, height + fPad.top + fPad.bottom);
2640 	}
2641 
2642 	if (moveItems) {
2643 		for (int32 i = 0; i < fItems.CountItems(); i++)
2644 			ItemAt(i)->fBounds.bottom = frame.bottom;
2645 	}
2646 
2647 	if (bestFit)
2648 		frame.right = ceilf(frame.right);
2649 	else
2650 		frame.right = Bounds().right;
2651 }
2652 
2653 
2654 void
2655 BMenu::_ComputeMatrixLayout(BRect &frame)
2656 {
2657 	frame.Set(0, 0, 0, 0);
2658 	for (int32 i = 0; i < CountItems(); i++) {
2659 		BMenuItem* item = ItemAt(i);
2660 		if (item != NULL) {
2661 			frame.left = std::min(frame.left, item->Frame().left);
2662 			frame.right = std::max(frame.right, item->Frame().right);
2663 			frame.top = std::min(frame.top, item->Frame().top);
2664 			frame.bottom = std::max(frame.bottom, item->Frame().bottom);
2665 		}
2666 	}
2667 }
2668 
2669 
2670 void
2671 BMenu::LayoutInvalidated(bool descendants)
2672 {
2673 	fUseCachedMenuLayout = false;
2674 	fLayoutData->preferred.Set(B_SIZE_UNSET, B_SIZE_UNSET);
2675 }
2676 
2677 
2678 // Assumes the SuperMenu to be locked (due to calling ConvertToScreen())
2679 BPoint
2680 BMenu::ScreenLocation()
2681 {
2682 	BMenu* superMenu = Supermenu();
2683 	BMenuItem* superItem = Superitem();
2684 
2685 	if (superMenu == NULL || superItem == NULL) {
2686 		debugger("BMenu can't determine where to draw."
2687 			"Override BMenu::ScreenLocation() to determine location.");
2688 	}
2689 
2690 	BPoint point;
2691 	if (superMenu->Layout() == B_ITEMS_IN_COLUMN)
2692 		point = superItem->Frame().RightTop() + BPoint(1.0f, 1.0f);
2693 	else
2694 		point = superItem->Frame().LeftBottom() + BPoint(1.0f, 1.0f);
2695 
2696 	superMenu->ConvertToScreen(&point);
2697 
2698 	return point;
2699 }
2700 
2701 
2702 BRect
2703 BMenu::_CalcFrame(BPoint where, bool* scrollOn)
2704 {
2705 	// TODO: Improve me
2706 	BRect bounds = Bounds();
2707 	BRect frame = bounds.OffsetToCopy(where);
2708 
2709 	BScreen screen(Window());
2710 	BRect screenFrame = screen.Frame();
2711 
2712 	BMenu* superMenu = Supermenu();
2713 	BMenuItem* superItem = Superitem();
2714 
2715 	// Reset frame shifted state since this menu is being redrawn
2716 	fExtraMenuData->frameShiftedLeft = false;
2717 
2718 	// TODO: Horrible hack:
2719 	// When added to a BMenuField, a BPopUpMenu is the child of
2720 	// a _BMCMenuBar_ to "fake" the menu hierarchy
2721 	bool inMenuField = dynamic_cast<_BMCMenuBar_*>(superMenu) != NULL;
2722 
2723 	// Offset the menu field menu window left by the width of the checkmark
2724 	// so that the text when the menu is closed lines up with the text when
2725 	// the menu is open.
2726 	if (inMenuField)
2727 		frame.OffsetBy(-8.0f, 0.0f);
2728 
2729 	bool scroll = false;
2730 	if (superMenu == NULL || superItem == NULL || inMenuField) {
2731 		// just move the window on screen
2732 		if (frame.bottom > screenFrame.bottom)
2733 			frame.OffsetBy(0, screenFrame.bottom - frame.bottom);
2734 		else if (frame.top < screenFrame.top)
2735 			frame.OffsetBy(0, -frame.top);
2736 
2737 		if (frame.right > screenFrame.right) {
2738 			frame.OffsetBy(screenFrame.right - frame.right, 0);
2739 			fExtraMenuData->frameShiftedLeft = true;
2740 		}
2741 		else if (frame.left < screenFrame.left)
2742 			frame.OffsetBy(-frame.left, 0);
2743 	} else if (superMenu->Layout() == B_ITEMS_IN_COLUMN) {
2744 		if (frame.right > screenFrame.right
2745 				|| superMenu->fExtraMenuData->frameShiftedLeft) {
2746 			frame.OffsetBy(-superItem->Frame().Width() - frame.Width() - 2, 0);
2747 			fExtraMenuData->frameShiftedLeft = true;
2748 		}
2749 
2750 		if (frame.left < 0)
2751 			frame.OffsetBy(-frame.left + 6, 0);
2752 
2753 		if (frame.bottom > screenFrame.bottom)
2754 			frame.OffsetBy(0, screenFrame.bottom - frame.bottom);
2755 	} else {
2756 		if (frame.bottom > screenFrame.bottom) {
2757 			frame.OffsetBy(0, -superItem->Frame().Height()
2758 				- frame.Height() - 3);
2759 		}
2760 
2761 		if (frame.right > screenFrame.right)
2762 			frame.OffsetBy(screenFrame.right - frame.right, 0);
2763 	}
2764 
2765 	if (!scroll) {
2766 		// basically, if this returns false, it means
2767 		// that the menu frame won't fit completely inside the screen
2768 		// TODO: Scrolling will currently only work up/down,
2769 		// not left/right
2770 		scroll = screenFrame.Height() < frame.Height();
2771 	}
2772 
2773 	if (scrollOn != NULL)
2774 		*scrollOn = scroll;
2775 
2776 	return frame;
2777 }
2778 
2779 
2780 void
2781 BMenu::DrawItems(BRect updateRect)
2782 {
2783 	int32 itemCount = fItems.CountItems();
2784 	for (int32 i = 0; i < itemCount; i++) {
2785 		BMenuItem* item = ItemAt(i);
2786 		if (item->Frame().Intersects(updateRect))
2787 			item->Draw();
2788 	}
2789 }
2790 
2791 
2792 int
2793 BMenu::_State(BMenuItem** item) const
2794 {
2795 	if (fState == MENU_STATE_TRACKING || fState == MENU_STATE_CLOSED)
2796 		return fState;
2797 
2798 	if (fSelected != NULL && fSelected->Submenu() != NULL)
2799 		return fSelected->Submenu()->_State(item);
2800 
2801 	return fState;
2802 }
2803 
2804 
2805 void
2806 BMenu::_InvokeItem(BMenuItem* item, bool now)
2807 {
2808 	if (!item->IsEnabled())
2809 		return;
2810 
2811 	// Do the "selected" animation
2812 	// TODO: Doesn't work. This is supposed to highlight
2813 	// and dehighlight the item, works on beos but not on haiku.
2814 	if (!item->Submenu() && LockLooper()) {
2815 		snooze(50000);
2816 		item->Select(true);
2817 		Window()->UpdateIfNeeded();
2818 		snooze(50000);
2819 		item->Select(false);
2820 		Window()->UpdateIfNeeded();
2821 		snooze(50000);
2822 		item->Select(true);
2823 		Window()->UpdateIfNeeded();
2824 		snooze(50000);
2825 		item->Select(false);
2826 		Window()->UpdateIfNeeded();
2827 		UnlockLooper();
2828 	}
2829 
2830 	// Lock the root menu window before calling BMenuItem::Invoke()
2831 	BMenu* parent = this;
2832 	BMenu* rootMenu = NULL;
2833 	do {
2834 		rootMenu = parent;
2835 		parent = rootMenu->Supermenu();
2836 	} while (parent != NULL);
2837 
2838 	if (rootMenu->LockLooper()) {
2839 		item->Invoke();
2840 		rootMenu->UnlockLooper();
2841 	}
2842 }
2843 
2844 
2845 bool
2846 BMenu::_OverSuper(BPoint location)
2847 {
2848 	if (!Supermenu())
2849 		return false;
2850 
2851 	return fSuperbounds.Contains(location);
2852 }
2853 
2854 
2855 bool
2856 BMenu::_OverSubmenu(BMenuItem* item, BPoint loc)
2857 {
2858 	if (item == NULL)
2859 		return false;
2860 
2861 	BMenu* subMenu = item->Submenu();
2862 	if (subMenu == NULL || subMenu->Window() == NULL)
2863 		return false;
2864 
2865 	// assume that loc is in screen coordinates
2866 	if (subMenu->Window()->Frame().Contains(loc))
2867 		return true;
2868 
2869 	return subMenu->_OverSubmenu(subMenu->fSelected, loc);
2870 }
2871 
2872 
2873 BMenuWindow*
2874 BMenu::_MenuWindow()
2875 {
2876 #if USE_CACHED_MENUWINDOW
2877 	if (fCachedMenuWindow == NULL) {
2878 		char windowName[64];
2879 		snprintf(windowName, 64, "%s cached menu", Name());
2880 		fCachedMenuWindow = new (nothrow) BMenuWindow(windowName);
2881 	}
2882 #endif
2883 	return fCachedMenuWindow;
2884 }
2885 
2886 
2887 void
2888 BMenu::_DeleteMenuWindow()
2889 {
2890 	if (fCachedMenuWindow != NULL) {
2891 		fCachedMenuWindow->Lock();
2892 		fCachedMenuWindow->Quit();
2893 		fCachedMenuWindow = NULL;
2894 	}
2895 }
2896 
2897 
2898 BMenuItem*
2899 BMenu::_HitTestItems(BPoint where, BPoint slop) const
2900 {
2901 	// TODO: Take "slop" into account ?
2902 
2903 	// if the point doesn't lie within the menu's
2904 	// bounds, bail out immediately
2905 	if (!Bounds().Contains(where))
2906 		return NULL;
2907 
2908 	int32 itemCount = CountItems();
2909 	for (int32 i = 0; i < itemCount; i++) {
2910 		BMenuItem* item = ItemAt(i);
2911 		if (item->Frame().Contains(where)
2912 			&& dynamic_cast<BSeparatorItem*>(item) == NULL) {
2913 			return item;
2914 		}
2915 	}
2916 
2917 	return NULL;
2918 }
2919 
2920 
2921 BRect
2922 BMenu::_Superbounds() const
2923 {
2924 	return fSuperbounds;
2925 }
2926 
2927 
2928 void
2929 BMenu::_CacheFontInfo()
2930 {
2931 	font_height fh;
2932 	GetFontHeight(&fh);
2933 	fAscent = fh.ascent;
2934 	fDescent = fh.descent;
2935 	fFontHeight = ceilf(fh.ascent + fh.descent + fh.leading);
2936 }
2937 
2938 
2939 void
2940 BMenu::_ItemMarked(BMenuItem* item)
2941 {
2942 	if (IsRadioMode()) {
2943 		for (int32 i = 0; i < CountItems(); i++) {
2944 			if (ItemAt(i) != item)
2945 				ItemAt(i)->SetMarked(false);
2946 		}
2947 	}
2948 
2949 	if (IsLabelFromMarked() && Superitem() != NULL)
2950 		Superitem()->SetLabel(item->Label());
2951 }
2952 
2953 
2954 void
2955 BMenu::_Install(BWindow* target)
2956 {
2957 	for (int32 i = 0; i < CountItems(); i++)
2958 		ItemAt(i)->Install(target);
2959 }
2960 
2961 
2962 void
2963 BMenu::_Uninstall()
2964 {
2965 	for (int32 i = 0; i < CountItems(); i++)
2966 		ItemAt(i)->Uninstall();
2967 }
2968 
2969 
2970 void
2971 BMenu::_SelectItem(BMenuItem* item, bool showSubmenu, bool selectFirstItem,
2972 	bool keyDown)
2973 {
2974 	// Avoid deselecting and then reselecting the same item
2975 	// which would cause flickering
2976 	if (item != fSelected) {
2977 		if (fSelected != NULL) {
2978 			fSelected->Select(false);
2979 			BMenu* subMenu = fSelected->Submenu();
2980 			if (subMenu != NULL && subMenu->Window() != NULL)
2981 				subMenu->_Hide();
2982 		}
2983 
2984 		fSelected = item;
2985 		if (fSelected != NULL)
2986 			fSelected->Select(true);
2987 	}
2988 
2989 	if (fSelected != NULL && showSubmenu) {
2990 		BMenu* subMenu = fSelected->Submenu();
2991 		if (subMenu != NULL && subMenu->Window() == NULL) {
2992 			if (!subMenu->_Show(selectFirstItem, keyDown)) {
2993 				// something went wrong, deselect the item
2994 				fSelected->Select(false);
2995 				fSelected = NULL;
2996 			}
2997 		}
2998 	}
2999 }
3000 
3001 
3002 bool
3003 BMenu::_SelectNextItem(BMenuItem* item, bool forward)
3004 {
3005 	if (CountItems() == 0) // cannot select next item in an empty menu
3006 		return false;
3007 
3008 	BMenuItem* nextItem = _NextItem(item, forward);
3009 	if (nextItem == NULL)
3010 		return false;
3011 
3012 	_SelectItem(nextItem, dynamic_cast<BMenuBar*>(this) != NULL);
3013 
3014 	if (LockLooper()) {
3015 		be_app->ObscureCursor();
3016 		UnlockLooper();
3017 	}
3018 
3019 	return true;
3020 }
3021 
3022 
3023 BMenuItem*
3024 BMenu::_NextItem(BMenuItem* item, bool forward) const
3025 {
3026 	const int32 numItems = fItems.CountItems();
3027 	if (numItems == 0)
3028 		return NULL;
3029 
3030 	int32 index = fItems.IndexOf(item);
3031 	int32 loopCount = numItems;
3032 	while (--loopCount) {
3033 		// Cycle through menu items in the given direction...
3034 		if (forward)
3035 			index++;
3036 		else
3037 			index--;
3038 
3039 		// ... wrap around...
3040 		if (index < 0)
3041 			index = numItems - 1;
3042 		else if (index >= numItems)
3043 			index = 0;
3044 
3045 		// ... and return the first suitable item found.
3046 		BMenuItem* nextItem = ItemAt(index);
3047 		if (nextItem->IsEnabled())
3048 			return nextItem;
3049 	}
3050 
3051 	// If no other suitable item was found, return NULL.
3052 	return NULL;
3053 }
3054 
3055 
3056 void
3057 BMenu::_SetStickyMode(bool sticky)
3058 {
3059 	if (fStickyMode == sticky)
3060 		return;
3061 
3062 	fStickyMode = sticky;
3063 
3064 	if (fSuper != NULL) {
3065 		// propagate the status to the super menu
3066 		fSuper->_SetStickyMode(sticky);
3067 	} else {
3068 		// TODO: Ugly hack, but it needs to be done in this method
3069 		BMenuBar* menuBar = dynamic_cast<BMenuBar*>(this);
3070 		if (sticky && menuBar != NULL && menuBar->LockLooper()) {
3071 			// If we are switching to sticky mode,
3072 			// steal the focus from the current focus view
3073 			// (needed to handle keyboard navigation)
3074 			menuBar->_StealFocus();
3075 			menuBar->UnlockLooper();
3076 		}
3077 	}
3078 }
3079 
3080 
3081 bool
3082 BMenu::_IsStickyMode() const
3083 {
3084 	return fStickyMode;
3085 }
3086 
3087 
3088 void
3089 BMenu::_GetShiftKey(uint32 &value) const
3090 {
3091 	// TODO: Move into init_interface_kit().
3092 	// Currently we can't do that, as get_modifier_key() blocks forever
3093 	// when called on input_server initialization, since it tries
3094 	// to send a synchronous message to itself (input_server is
3095 	// a BApplication)
3096 
3097 	if (get_modifier_key(B_LEFT_SHIFT_KEY, &value) != B_OK)
3098 		value = 0x4b;
3099 }
3100 
3101 
3102 void
3103 BMenu::_GetControlKey(uint32 &value) const
3104 {
3105 	// TODO: Move into init_interface_kit().
3106 	// Currently we can't do that, as get_modifier_key() blocks forever
3107 	// when called on input_server initialization, since it tries
3108 	// to send a synchronous message to itself (input_server is
3109 	// a BApplication)
3110 
3111 	if (get_modifier_key(B_LEFT_CONTROL_KEY, &value) != B_OK)
3112 		value = 0x5c;
3113 }
3114 
3115 
3116 void
3117 BMenu::_GetCommandKey(uint32 &value) const
3118 {
3119 	// TODO: Move into init_interface_kit().
3120 	// Currently we can't do that, as get_modifier_key() blocks forever
3121 	// when called on input_server initialization, since it tries
3122 	// to send a synchronous message to itself (input_server is
3123 	// a BApplication)
3124 
3125 	if (get_modifier_key(B_LEFT_COMMAND_KEY, &value) != B_OK)
3126 		value = 0x66;
3127 }
3128 
3129 
3130 void
3131 BMenu::_GetOptionKey(uint32 &value) const
3132 {
3133 	// TODO: Move into init_interface_kit().
3134 	// Currently we can't do that, as get_modifier_key() blocks forever
3135 	// when called on input_server initialization, since it tries
3136 	// to send a synchronous message to itself (input_server is
3137 	// a BApplication)
3138 
3139 	if (get_modifier_key(B_LEFT_OPTION_KEY, &value) != B_OK)
3140 		value = 0x5d;
3141 }
3142 
3143 
3144 void
3145 BMenu::_GetMenuKey(uint32 &value) const
3146 {
3147 	// TODO: Move into init_interface_kit().
3148 	// Currently we can't do that, as get_modifier_key() blocks forever
3149 	// when called on input_server initialization, since it tries
3150 	// to send a synchronous message to itself (input_server is
3151 	// a BApplication)
3152 
3153 	if (get_modifier_key(B_MENU_KEY, &value) != B_OK)
3154 		value = 0x68;
3155 }
3156 
3157 
3158 void
3159 BMenu::_CalcTriggers()
3160 {
3161 	BPrivate::TriggerList triggerList;
3162 
3163 	// Gathers the existing triggers set by the user
3164 	for (int32 i = 0; i < CountItems(); i++) {
3165 		char trigger = ItemAt(i)->Trigger();
3166 		if (trigger != 0)
3167 			triggerList.AddTrigger(trigger);
3168 	}
3169 
3170 	// Set triggers for items which don't have one yet
3171 	for (int32 i = 0; i < CountItems(); i++) {
3172 		BMenuItem* item = ItemAt(i);
3173 		if (item->Trigger() == 0) {
3174 			uint32 trigger;
3175 			int32 index;
3176 			if (_ChooseTrigger(item->Label(), index, trigger, triggerList))
3177 				item->SetAutomaticTrigger(index, trigger);
3178 		}
3179 	}
3180 }
3181 
3182 
3183 bool
3184 BMenu::_ChooseTrigger(const char* title, int32& index, uint32& trigger,
3185 	BPrivate::TriggerList& triggers)
3186 {
3187 	if (title == NULL)
3188 		return false;
3189 
3190 	index = 0;
3191 	uint32 c;
3192 	const char* nextCharacter, *character;
3193 
3194 	// two runs: first we look out for alphanumeric ASCII characters
3195 	nextCharacter = title;
3196 	character = nextCharacter;
3197 	while ((c = BUnicodeChar::FromUTF8(&nextCharacter)) != 0) {
3198 		if (!(c < 128 && BUnicodeChar::IsAlNum(c)) || triggers.HasTrigger(c)) {
3199 			character = nextCharacter;
3200 			continue;
3201 		}
3202 		trigger = BUnicodeChar::ToLower(c);
3203 		index = (int32)(character - title);
3204 		return triggers.AddTrigger(c);
3205 	}
3206 
3207 	// then, if we still haven't found something, we accept anything
3208 	nextCharacter = title;
3209 	character = nextCharacter;
3210 	while ((c = BUnicodeChar::FromUTF8(&nextCharacter)) != 0) {
3211 		if (BUnicodeChar::IsSpace(c) || triggers.HasTrigger(c)) {
3212 			character = nextCharacter;
3213 			continue;
3214 		}
3215 		trigger = BUnicodeChar::ToLower(c);
3216 		index = (int32)(character - title);
3217 		return triggers.AddTrigger(c);
3218 	}
3219 
3220 	return false;
3221 }
3222 
3223 
3224 void
3225 BMenu::_UpdateWindowViewSize(const bool &move)
3226 {
3227 	BMenuWindow* window = static_cast<BMenuWindow*>(Window());
3228 	if (window == NULL)
3229 		return;
3230 
3231 	if (dynamic_cast<BMenuBar*>(this) != NULL)
3232 		return;
3233 
3234 	if (!fResizeToFit)
3235 		return;
3236 
3237 	bool scroll = false;
3238 	const BPoint screenLocation = move ? ScreenLocation()
3239 		: window->Frame().LeftTop();
3240 	BRect frame = _CalcFrame(screenLocation, &scroll);
3241 	ResizeTo(frame.Width(), frame.Height());
3242 
3243 	if (fItems.CountItems() > 0) {
3244 		if (!scroll) {
3245 			if (fLayout == B_ITEMS_IN_COLUMN)
3246 				window->DetachScrollers();
3247 
3248 			window->ResizeTo(Bounds().Width(), Bounds().Height());
3249 		} else {
3250 			BScreen screen(window);
3251 
3252 			// Only scroll on menus not attached to a menubar, or when the
3253 			// menu frame is above the visible screen
3254 			if (dynamic_cast<BMenuBar*>(Supermenu()) == NULL || frame.top < 0) {
3255 
3256 				// If we need scrolling, resize the window to fit the screen and
3257 				// attach scrollers to our cached BMenuWindow.
3258 				window->ResizeTo(Bounds().Width(), screen.Frame().Height());
3259 				frame.top = 0;
3260 
3261 				// we currently only support scrolling for B_ITEMS_IN_COLUMN
3262 				if (fLayout == B_ITEMS_IN_COLUMN) {
3263 					window->AttachScrollers();
3264 
3265 					BMenuItem* selectedItem = FindMarked();
3266 					if (selectedItem != NULL) {
3267 						// scroll to the selected item
3268 						if (Supermenu() == NULL) {
3269 							window->TryScrollTo(selectedItem->Frame().top);
3270 						} else {
3271 							BPoint point = selectedItem->Frame().LeftTop();
3272 							BPoint superPoint = Superitem()->Frame().LeftTop();
3273 							Supermenu()->ConvertToScreen(&superPoint);
3274 							ConvertToScreen(&point);
3275 							window->TryScrollTo(point.y - superPoint.y);
3276 						}
3277 					}
3278 				}
3279 			}
3280 		}
3281 	} else {
3282 		_CacheFontInfo();
3283 		window->ResizeTo(StringWidth(BPrivate::kEmptyMenuLabel)
3284 				+ fPad.left + fPad.right,
3285 			fFontHeight + fPad.top + fPad.bottom);
3286 	}
3287 
3288 	if (move)
3289 		window->MoveTo(frame.LeftTop());
3290 }
3291 
3292 
3293 bool
3294 BMenu::_AddDynamicItems(bool keyDown)
3295 {
3296 	bool addAborted = false;
3297 	if (AddDynamicItem(B_INITIAL_ADD)) {
3298 		BMenuItem* superItem = Superitem();
3299 		BMenu* superMenu = Supermenu();
3300 		do {
3301 			if (superMenu != NULL
3302 				&& !superMenu->_OkToProceed(superItem, keyDown)) {
3303 				AddDynamicItem(B_ABORT);
3304 				addAborted = true;
3305 				break;
3306 			}
3307 		} while (AddDynamicItem(B_PROCESSING));
3308 	}
3309 
3310 	return addAborted;
3311 }
3312 
3313 
3314 bool
3315 BMenu::_OkToProceed(BMenuItem* item, bool keyDown)
3316 {
3317 	BPoint where;
3318 	uint32 buttons;
3319 	GetMouse(&where, &buttons, false);
3320 	bool stickyMode = _IsStickyMode();
3321 	// Quit if user clicks the mouse button in sticky mode
3322 	// or releases the mouse button in nonsticky mode
3323 	// or moves the pointer over another item
3324 	// TODO: I added the check for BMenuBar to solve a problem with Deskbar.
3325 	// BeOS seems to do something similar. This could also be a bug in
3326 	// Deskbar, though.
3327 	if ((buttons != 0 && stickyMode)
3328 		|| ((dynamic_cast<BMenuBar*>(this) == NULL
3329 			&& (buttons == 0 && !stickyMode))
3330 		|| ((_HitTestItems(where) != item) && !keyDown))) {
3331 		return false;
3332 	}
3333 
3334 	return true;
3335 }
3336 
3337 
3338 bool
3339 BMenu::_CustomTrackingWantsToQuit()
3340 {
3341 	if (fExtraMenuData != NULL && fExtraMenuData->trackingHook != NULL
3342 		&& fExtraMenuData->trackingState != NULL) {
3343 		return fExtraMenuData->trackingHook(this,
3344 			fExtraMenuData->trackingState);
3345 	}
3346 
3347 	return false;
3348 }
3349 
3350 
3351 void
3352 BMenu::_QuitTracking(bool onlyThis)
3353 {
3354 	_SelectItem(NULL);
3355 	if (BMenuBar* menuBar = dynamic_cast<BMenuBar*>(this))
3356 		menuBar->_RestoreFocus();
3357 
3358 	fState = MENU_STATE_CLOSED;
3359 
3360 	if (!onlyThis) {
3361 		// Close the whole menu hierarchy
3362 		if (Supermenu() != NULL)
3363 			Supermenu()->fState = MENU_STATE_CLOSED;
3364 
3365 		if (_IsStickyMode())
3366 			_SetStickyMode(false);
3367 
3368 		if (LockLooper()) {
3369 			be_app->ShowCursor();
3370 			UnlockLooper();
3371 		}
3372 	}
3373 
3374 	_Hide();
3375 }
3376 
3377 
3378 //	#pragma mark - menu_info functions
3379 
3380 
3381 // TODO: Maybe the following two methods would fit better into
3382 // InterfaceDefs.cpp
3383 // In R5, they do all the work client side, we let the app_server handle the
3384 // details.
3385 status_t
3386 set_menu_info(menu_info* info)
3387 {
3388 	if (!info)
3389 		return B_BAD_VALUE;
3390 
3391 	BPrivate::AppServerLink link;
3392 	link.StartMessage(AS_SET_MENU_INFO);
3393 	link.Attach<menu_info>(*info);
3394 
3395 	status_t status = B_ERROR;
3396 	if (link.FlushWithReply(status) == B_OK && status == B_OK)
3397 		BMenu::sMenuInfo = *info;
3398 		// Update also the local copy, in case anyone relies on it
3399 
3400 	return status;
3401 }
3402 
3403 
3404 status_t
3405 get_menu_info(menu_info* info)
3406 {
3407 	if (!info)
3408 		return B_BAD_VALUE;
3409 
3410 	BPrivate::AppServerLink link;
3411 	link.StartMessage(AS_GET_MENU_INFO);
3412 
3413 	status_t status = B_ERROR;
3414 	if (link.FlushWithReply(status) == B_OK && status == B_OK)
3415 		link.Read<menu_info>(info);
3416 
3417 	return status;
3418 }
3419 
3420 
3421 extern "C" void
3422 B_IF_GCC_2(InvalidateLayout__5BMenub,_ZN5BMenu16InvalidateLayoutEb)(
3423 	BMenu* menu, bool descendants)
3424 {
3425 	menu->InvalidateLayout();
3426 }
3427