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