xref: /haiku/src/kits/interface/Menu.cpp (revision 72156a402f54ea4be9dc3e3e9704c612f7d9ad16)
1 /*
2  * Copyright 2001-2011, Haiku Inc. All rights reserved.
3  * Distributed under the terms of the MIT license.
4  *
5  * Authors:
6  *		Marc Flerackers (mflerackers@androme.be)
7  *		Stefano Ceccherini (stefano.ceccherini@gmail.com)
8  *		Rene Gollent (anevilyak@gmail.com)
9  *		Stephan Aßmus <superstippi@gmx.de>
10  */
11 
12 
13 #include <Menu.h>
14 
15 #include <new>
16 #include <ctype.h>
17 #include <string.h>
18 
19 #include <Bitmap.h>
20 #include <Catalog.h>
21 #include <ControlLook.h>
22 #include <Debug.h>
23 #include <File.h>
24 #include <FindDirectory.h>
25 #include <Layout.h>
26 #include <LayoutUtils.h>
27 #include <LocaleBackend.h>
28 #include <MenuBar.h>
29 #include <MenuItem.h>
30 #include <Messenger.h>
31 #include <Path.h>
32 #include <PropertyInfo.h>
33 #include <Screen.h>
34 #include <ScrollBar.h>
35 #include <Window.h>
36 
37 #include <AppServerLink.h>
38 #include <binary_compatibility/Interface.h>
39 #include <BMCPrivate.h>
40 #include <MenuPrivate.h>
41 #include <MenuWindow.h>
42 #include <ServerProtocol.h>
43 
44 #include "utf8_functions.h"
45 
46 
47 #define USE_CACHED_MENUWINDOW 1
48 
49 using BPrivate::gLocaleBackend;
50 using BPrivate::LocaleBackend;
51 
52 #undef B_TRANSLATE_CONTEXT
53 #define B_TRANSLATE_CONTEXT "Menu"
54 
55 #undef B_TRANSLATE
56 #define B_TRANSLATE(str) \
57 	gLocaleBackend->GetString(B_TRANSLATE_MARK(str), "Menu")
58 
59 
60 using std::nothrow;
61 using BPrivate::BMenuWindow;
62 
63 namespace BPrivate {
64 
65 class TriggerList {
66 public:
67 	TriggerList() {}
68 	~TriggerList() {}
69 
70 	// TODO: make this work with Unicode characters!
71 
72 	bool HasTrigger(uint32 c)
73 		{ return fList.HasItem((void*)tolower(c)); }
74 	bool AddTrigger(uint32 c)
75 		{ return fList.AddItem((void*)tolower(c)); }
76 
77 private:
78 	BList	fList;
79 };
80 
81 
82 class ExtraMenuData {
83 public:
84 	menu_tracking_hook	trackingHook;
85 	void*				trackingState;
86 
87 	ExtraMenuData(menu_tracking_hook func, void* state)
88 	{
89 		trackingHook = func;
90 		trackingState = state;
91 	}
92 };
93 
94 
95 }	// namespace BPrivate
96 
97 
98 menu_info BMenu::sMenuInfo;
99 bool BMenu::sAltAsCommandKey;
100 
101 
102 static property_info sPropList[] = {
103 	{ "Enabled", { B_GET_PROPERTY, 0 },
104 		{ B_DIRECT_SPECIFIER, 0 }, "Returns true if menu or menu item is "
105 		"enabled; false otherwise.",
106 		0, { B_BOOL_TYPE }
107 	},
108 
109 	{ "Enabled", { B_SET_PROPERTY, 0 },
110 		{ B_DIRECT_SPECIFIER, 0 }, "Enables or disables menu or menu item.",
111 		0, { B_BOOL_TYPE }
112 	},
113 
114 	{ "Label", { B_GET_PROPERTY, 0 },
115 		{ B_DIRECT_SPECIFIER, 0 }, "Returns the string label of the menu or "
116 		"menu item.",
117 		0, { B_STRING_TYPE }
118 	},
119 
120 	{ "Label", { B_SET_PROPERTY, 0 },
121 		{ B_DIRECT_SPECIFIER, 0 }, "Sets the string label of the menu or menu "
122 		"item.",
123 		0, { B_STRING_TYPE }
124 	},
125 
126 	{ "Mark", { B_GET_PROPERTY, 0 },
127 		{ B_DIRECT_SPECIFIER, 0 }, "Returns true if the menu item or the "
128 		"menu's superitem is marked; false otherwise.",
129 		0, { B_BOOL_TYPE }
130 	},
131 
132 	{ "Mark", { B_SET_PROPERTY, 0 },
133 		{ B_DIRECT_SPECIFIER, 0 }, "Marks or unmarks the menu item or the "
134 		"menu's superitem.",
135 		0, { B_BOOL_TYPE }
136 	},
137 
138 	{ "Menu", { B_CREATE_PROPERTY, 0 },
139 		{ B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 },
140 		"Adds a new menu item at the specified index with the text label "
141 		"found in \"data\" and the int32 command found in \"what\" (used as "
142 		"the what field in the BMessage sent by the item)." , 0, {},
143 		{ 	{{{"data", B_STRING_TYPE}}}
144 		}
145 	},
146 
147 	{ "Menu", { B_DELETE_PROPERTY, 0 },
148 		{ B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 },
149 		"Removes the selected menu or menus.", 0, {}
150 	},
151 
152 	{ "Menu", { },
153 		{ B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 },
154 		"Directs scripting message to the specified menu, first popping the "
155 		"current specifier off the stack.", 0, {}
156 	},
157 
158 	{ "MenuItem", { B_COUNT_PROPERTIES, 0 },
159 		{ B_DIRECT_SPECIFIER, 0 }, "Counts the number of menu items in the "
160 		"specified menu.",
161 		0, { B_INT32_TYPE }
162 	},
163 
164 	{ "MenuItem", { B_CREATE_PROPERTY, 0 },
165 		{ B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 },
166 		"Adds a new menu item at the specified index with the text label "
167 		"found in \"data\" and the int32 command found in \"what\" (used as "
168 		"the what field in the BMessage sent by the item).", 0, {},
169 		{	{ {{"data", B_STRING_TYPE },
170 			{"be:invoke_message", B_MESSAGE_TYPE},
171 			{"what", B_INT32_TYPE},
172 			{"be:target", B_MESSENGER_TYPE}} }
173 		}
174 	},
175 
176 	{ "MenuItem", { B_DELETE_PROPERTY, 0 },
177 		{ B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 },
178 		"Removes the specified menu item from its parent menu."
179 	},
180 
181 	{ "MenuItem", { B_EXECUTE_PROPERTY, 0 },
182 		{ B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 },
183 		"Invokes the specified menu item."
184 	},
185 
186 	{ "MenuItem", { },
187 		{ B_NAME_SPECIFIER, B_INDEX_SPECIFIER, B_REVERSE_INDEX_SPECIFIER, 0 },
188 		"Directs scripting message to the specified menu, first popping the "
189 		"current specifier off the stack."
190 	},
191 
192 	{}
193 };
194 
195 
196 // note: this is redefined to localized one in BMenu::_InitData
197 const char* BPrivate::kEmptyMenuLabel = "<empty>";
198 
199 
200 struct BMenu::LayoutData {
201 	BSize	preferred;
202 	uint32	lastResizingMode;
203 };
204 
205 
206 // #pragma mark -
207 
208 
209 BMenu::BMenu(const char* name, menu_layout layout)
210 	:
211 	BView(BRect(0, 0, 0, 0), name, 0, B_WILL_DRAW),
212 	fChosenItem(NULL),
213 	fPad(14.0f, 2.0f, 20.0f, 0.0f),
214 	fSelected(NULL),
215 	fCachedMenuWindow(NULL),
216 	fSuper(NULL),
217 	fSuperitem(NULL),
218 	fAscent(-1.0f),
219 	fDescent(-1.0f),
220 	fFontHeight(-1.0f),
221 	fState(MENU_STATE_CLOSED),
222 	fLayout(layout),
223 	fExtraRect(NULL),
224 	fMaxContentWidth(0.0f),
225 	fInitMatrixSize(NULL),
226 	fExtraMenuData(NULL),
227 	fTrigger(0),
228 	fResizeToFit(true),
229 	fUseCachedMenuLayout(false),
230 	fEnabled(true),
231 	fDynamicName(false),
232 	fRadioMode(false),
233 	fTrackNewBounds(false),
234 	fStickyMode(false),
235 	fIgnoreHidden(true),
236 	fTriggerEnabled(true),
237 	fRedrawAfterSticky(false),
238 	fAttachAborted(false)
239 {
240 	_InitData(NULL);
241 }
242 
243 
244 BMenu::BMenu(const char* name, float width, float height)
245 	:
246 	BView(BRect(0.0f, 0.0f, 0.0f, 0.0f), name, 0, B_WILL_DRAW),
247 	fChosenItem(NULL),
248 	fSelected(NULL),
249 	fCachedMenuWindow(NULL),
250 	fSuper(NULL),
251 	fSuperitem(NULL),
252 	fAscent(-1.0f),
253 	fDescent(-1.0f),
254 	fFontHeight(-1.0f),
255 	fState(0),
256 	fLayout(B_ITEMS_IN_MATRIX),
257 	fExtraRect(NULL),
258 	fMaxContentWidth(0.0f),
259 	fInitMatrixSize(NULL),
260 	fExtraMenuData(NULL),
261 	fTrigger(0),
262 	fResizeToFit(true),
263 	fUseCachedMenuLayout(false),
264 	fEnabled(true),
265 	fDynamicName(false),
266 	fRadioMode(false),
267 	fTrackNewBounds(false),
268 	fStickyMode(false),
269 	fIgnoreHidden(true),
270 	fTriggerEnabled(true),
271 	fRedrawAfterSticky(false),
272 	fAttachAborted(false)
273 {
274 	_InitData(NULL);
275 }
276 
277 
278 BMenu::BMenu(BMessage* archive)
279 	:
280 	BView(archive),
281 	fChosenItem(NULL),
282 	fPad(14.0f, 2.0f, 20.0f, 0.0f),
283 	fSelected(NULL),
284 	fCachedMenuWindow(NULL),
285 	fSuper(NULL),
286 	fSuperitem(NULL),
287 	fAscent(-1.0f),
288 	fDescent(-1.0f),
289 	fFontHeight(-1.0f),
290 	fState(MENU_STATE_CLOSED),
291 	fLayout(B_ITEMS_IN_ROW),
292 	fExtraRect(NULL),
293 	fMaxContentWidth(0.0f),
294 	fInitMatrixSize(NULL),
295 	fExtraMenuData(NULL),
296 	fTrigger(0),
297 	fResizeToFit(true),
298 	fUseCachedMenuLayout(false),
299 	fEnabled(true),
300 	fDynamicName(false),
301 	fRadioMode(false),
302 	fTrackNewBounds(false),
303 	fStickyMode(false),
304 	fIgnoreHidden(true),
305 	fTriggerEnabled(true),
306 	fRedrawAfterSticky(false),
307 	fAttachAborted(false)
308 {
309 	_InitData(archive);
310 }
311 
312 
313 BMenu::~BMenu()
314 {
315 	_DeleteMenuWindow();
316 
317 	RemoveItems(0, CountItems(), true);
318 
319 	delete fInitMatrixSize;
320 	delete fExtraMenuData;
321 	delete fLayoutData;
322 }
323 
324 
325 // #pragma mark -
326 
327 
328 BArchivable*
329 BMenu::Instantiate(BMessage* archive)
330 {
331 	if (validate_instantiation(archive, "BMenu"))
332 		return new (nothrow) BMenu(archive);
333 
334 	return NULL;
335 }
336 
337 
338 status_t
339 BMenu::Archive(BMessage* data, bool deep) const
340 {
341 	status_t err = BView::Archive(data, deep);
342 
343 	if (err == B_OK && Layout() != B_ITEMS_IN_ROW)
344 		err = data->AddInt32("_layout", Layout());
345 	if (err == B_OK)
346 		err = data->AddBool("_rsize_to_fit", fResizeToFit);
347 	if (err == B_OK)
348 		err = data->AddBool("_disable", !IsEnabled());
349 	if (err ==  B_OK)
350 		err = data->AddBool("_radio", IsRadioMode());
351 	if (err == B_OK)
352 		err = data->AddBool("_trig_disabled", AreTriggersEnabled());
353 	if (err == B_OK)
354 		err = data->AddBool("_dyn_label", fDynamicName);
355 	if (err == B_OK)
356 		err = data->AddFloat("_maxwidth", fMaxContentWidth);
357 	if (err == B_OK && deep) {
358 		BMenuItem* item = NULL;
359 		int32 index = 0;
360 		while ((item = ItemAt(index++)) != NULL) {
361 			BMessage itemData;
362 			item->Archive(&itemData, deep);
363 			err = data->AddMessage("_items", &itemData);
364 			if (err != B_OK)
365 				break;
366 			if (fLayout == B_ITEMS_IN_MATRIX) {
367 				err = data->AddRect("_i_frames", item->fBounds);
368 			}
369 		}
370 	}
371 
372 	return err;
373 }
374 
375 
376 // #pragma mark -
377 
378 
379 void
380 BMenu::AttachedToWindow()
381 {
382 	BView::AttachedToWindow();
383 
384 	_GetIsAltCommandKey(sAltAsCommandKey);
385 
386 	fAttachAborted = _AddDynamicItems();
387 
388 	if (!fAttachAborted) {
389 		_CacheFontInfo();
390 		_LayoutItems(0);
391 		_UpdateWindowViewSize(false);
392 	}
393 }
394 
395 
396 void
397 BMenu::DetachedFromWindow()
398 {
399 	BView::DetachedFromWindow();
400 }
401 
402 
403 void
404 BMenu::AllAttached()
405 {
406 	BView::AllAttached();
407 }
408 
409 
410 void
411 BMenu::AllDetached()
412 {
413 	BView::AllDetached();
414 }
415 
416 
417 // #pragma mark -
418 
419 
420 void
421 BMenu::Draw(BRect updateRect)
422 {
423 	if (_RelayoutIfNeeded()) {
424 		Invalidate();
425 		return;
426 	}
427 
428 	DrawBackground(updateRect);
429 	_DrawItems(updateRect);
430 }
431 
432 
433 void
434 BMenu::MessageReceived(BMessage* msg)
435 {
436 	switch (msg->what) {
437 		case B_MOUSE_WHEEL_CHANGED:
438 		{
439 			float deltaY = 0;
440 			msg->FindFloat("be:wheel_delta_y", &deltaY);
441 			if (deltaY == 0)
442 				return;
443 
444 			BMenuWindow* window = dynamic_cast<BMenuWindow*>(Window());
445 			if (window == NULL)
446 				return;
447 
448 			float largeStep;
449 			float smallStep;
450 			window->GetSteps(&smallStep, &largeStep);
451 
452 			// pressing the option/command/control key scrolls faster
453 			if (modifiers() & (B_OPTION_KEY | B_COMMAND_KEY | B_CONTROL_KEY))
454 				deltaY *= largeStep;
455 			else
456 				deltaY *= smallStep;
457 
458 			window->TryScrollBy(deltaY);
459 			break;
460 		}
461 
462 		default:
463 			BView::MessageReceived(msg);
464 			break;
465 	}
466 }
467 
468 
469 void
470 BMenu::KeyDown(const char* bytes, int32 numBytes)
471 {
472 	// TODO: Test how it works on beos and implement it correctly
473 	switch (bytes[0]) {
474 		case B_UP_ARROW:
475 			if (fLayout == B_ITEMS_IN_COLUMN)
476 				_SelectNextItem(fSelected, false);
477 			break;
478 
479 		case B_DOWN_ARROW:
480 			{
481 				BMenuBar* bar = dynamic_cast<BMenuBar*>(Supermenu());
482 				if (bar != NULL && fState == MENU_STATE_CLOSED) {
483 					// tell MenuBar's _Track:
484 					bar->fState = MENU_STATE_KEY_TO_SUBMENU;
485 				}
486 			}
487 			if (fLayout == B_ITEMS_IN_COLUMN)
488 				_SelectNextItem(fSelected, true);
489 			break;
490 
491 		case B_LEFT_ARROW:
492 			if (fLayout == B_ITEMS_IN_ROW)
493 				_SelectNextItem(fSelected, false);
494 			else {
495 				// this case has to be handled a bit specially.
496 				BMenuItem* item = Superitem();
497 				if (item) {
498 					if (dynamic_cast<BMenuBar*>(Supermenu())) {
499 						// If we're at the top menu below the menu bar, pass
500 						// the keypress to the menu bar so we can move to
501 						// another top level menu.
502 						BMessenger msgr(Supermenu());
503 						msgr.SendMessage(Window()->CurrentMessage());
504 					} else {
505 						// tell _Track
506 						fState = MENU_STATE_KEY_LEAVE_SUBMENU;
507 					}
508 				}
509 			}
510 			break;
511 
512 		case B_RIGHT_ARROW:
513 			if (fLayout == B_ITEMS_IN_ROW)
514 				_SelectNextItem(fSelected, true);
515 			else {
516 				if (fSelected && fSelected->Submenu()) {
517 					fSelected->Submenu()->_SetStickyMode(true);
518 						// fix me: this shouldn't be needed but dynamic menus
519 						// aren't getting it set correctly when keyboard
520 						// navigating, which aborts the attach
521 					fState = MENU_STATE_KEY_TO_SUBMENU;
522 					_SelectItem(fSelected, true, true, true);
523 				} else if (dynamic_cast<BMenuBar*>(Supermenu())) {
524 					// if we have no submenu and we're an
525 					// item in the top menu below the menubar,
526 					// pass the keypress to the menubar
527 					// so you can use the keypress to switch menus.
528 					BMessenger msgr(Supermenu());
529 					msgr.SendMessage(Window()->CurrentMessage());
530 				}
531 			}
532 			break;
533 
534 		case B_PAGE_UP:
535 		case B_PAGE_DOWN:
536 		{
537 			BMenuWindow* window = dynamic_cast<BMenuWindow*>(Window());
538 			if (window == NULL || !window->HasScrollers())
539 				break;
540 
541 			int32 deltaY = bytes[0] == B_PAGE_UP ? -1 : 1;
542 
543 			float largeStep;
544 			window->GetSteps(NULL, &largeStep);
545 			window->TryScrollBy(deltaY * largeStep);
546 			break;
547 		}
548 
549 		case B_ENTER:
550 		case B_SPACE:
551 			if (fSelected) {
552 				// preserve for exit handling
553 				fChosenItem = fSelected;
554 				_QuitTracking(false);
555 			}
556 			break;
557 
558 		case B_ESCAPE:
559 			_SelectItem(NULL);
560 			if (fState == MENU_STATE_CLOSED && dynamic_cast<BMenuBar*>(Supermenu())) {
561 				// Keyboard may show menu without tracking it
562 				BMessenger msgr(Supermenu());
563 				msgr.SendMessage(Window()->CurrentMessage());
564 			} else
565 				_QuitTracking(false);
566 			break;
567 
568 		default:
569 		{
570 			uint32 trigger = UTF8ToCharCode(&bytes);
571 
572 			for (uint32 i = CountItems(); i-- > 0;) {
573 				BMenuItem* item = ItemAt(i);
574 				if (item->fTriggerIndex < 0 || item->fTrigger != trigger)
575 					continue;
576 
577 				_InvokeItem(item);
578 				break;
579 			}
580 			break;
581 		}
582 	}
583 }
584 
585 
586 // #pragma mark -
587 
588 
589 BSize
590 BMenu::MinSize()
591 {
592 	_ValidatePreferredSize();
593 
594 	BSize size = (GetLayout() ? GetLayout()->MinSize()
595 		: fLayoutData->preferred);
596 	return BLayoutUtils::ComposeSize(ExplicitMinSize(), size);
597 }
598 
599 
600 BSize
601 BMenu::MaxSize()
602 {
603 	_ValidatePreferredSize();
604 
605 	BSize size = (GetLayout() ? GetLayout()->MaxSize()
606 		: fLayoutData->preferred);
607 	return BLayoutUtils::ComposeSize(ExplicitMaxSize(), size);
608 }
609 
610 
611 BSize
612 BMenu::PreferredSize()
613 {
614 	_ValidatePreferredSize();
615 
616 	BSize size = (GetLayout() ? GetLayout()->PreferredSize()
617 		: fLayoutData->preferred);
618 	return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), size);
619 }
620 
621 
622 void
623 BMenu::GetPreferredSize(float* _width, float* _height)
624 {
625 	_ValidatePreferredSize();
626 
627 	if (_width)
628 		*_width = fLayoutData->preferred.width;
629 	if (_height)
630 		*_height = fLayoutData->preferred.height;
631 }
632 
633 
634 void
635 BMenu::ResizeToPreferred()
636 {
637 	BView::ResizeToPreferred();
638 }
639 
640 
641 void
642 BMenu::DoLayout()
643 {
644 	// If the user set a layout, we let the base class version call its
645 	// hook.
646 	if (GetLayout()) {
647 		BView::DoLayout();
648 		return;
649 	}
650 
651 	if (_RelayoutIfNeeded())
652 		Invalidate();
653 }
654 
655 
656 void
657 BMenu::FrameMoved(BPoint new_position)
658 {
659 	BView::FrameMoved(new_position);
660 }
661 
662 
663 void
664 BMenu::FrameResized(float new_width, float new_height)
665 {
666 	BView::FrameResized(new_width, new_height);
667 }
668 
669 
670 void
671 BMenu::InvalidateLayout()
672 {
673 	InvalidateLayout(false);
674 }
675 
676 
677 void
678 BMenu::InvalidateLayout(bool descendants)
679 {
680 	fUseCachedMenuLayout = false;
681 	fLayoutData->preferred.Set(B_SIZE_UNSET, B_SIZE_UNSET);
682 
683 	BView::InvalidateLayout(descendants);
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_INVALIDATE_LAYOUT:
1240 		{
1241 			perform_data_invalidate_layout* data
1242 				= (perform_data_invalidate_layout*)_data;
1243 			BMenu::InvalidateLayout(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 	// we need to translate some strings, and in order to do so, we need
1431 	// to use the LocaleBackend to reach liblocale.so
1432 	if (gLocaleBackend == NULL)
1433 		LocaleBackend::LoadBackend();
1434 
1435 	BPrivate::kEmptyMenuLabel = B_TRANSLATE("<empty>");
1436 
1437 	// TODO: Get _color, _fname, _fflt from the message, if present
1438 	BFont font;
1439 	font.SetFamilyAndStyle(sMenuInfo.f_family, sMenuInfo.f_style);
1440 	font.SetSize(sMenuInfo.font_size);
1441 	SetFont(&font, B_FONT_FAMILY_AND_STYLE | B_FONT_SIZE);
1442 
1443 	fLayoutData = new LayoutData;
1444 	fLayoutData->lastResizingMode = ResizingMode();
1445 
1446 	SetLowColor(sMenuInfo.background_color);
1447 	SetViewColor(sMenuInfo.background_color);
1448 
1449 	fTriggerEnabled = sMenuInfo.triggers_always_shown;
1450 
1451 	if (archive != NULL) {
1452 		archive->FindInt32("_layout", (int32*)&fLayout);
1453 		archive->FindBool("_rsize_to_fit", &fResizeToFit);
1454 		bool disabled;
1455 		if (archive->FindBool("_disable", &disabled) == B_OK)
1456 			fEnabled = !disabled;
1457 		archive->FindBool("_radio", &fRadioMode);
1458 
1459 		bool disableTrigger = false;
1460 		archive->FindBool("_trig_disabled", &disableTrigger);
1461 		fTriggerEnabled = !disableTrigger;
1462 
1463 		archive->FindBool("_dyn_label", &fDynamicName);
1464 		archive->FindFloat("_maxwidth", &fMaxContentWidth);
1465 
1466 		BMessage msg;
1467 			for (int32 i = 0; archive->FindMessage("_items", i, &msg) == B_OK; i++) {
1468 			BArchivable* object = instantiate_object(&msg);
1469 			if (BMenuItem* item = dynamic_cast<BMenuItem*>(object)) {
1470 				BRect bounds;
1471 				if (fLayout == B_ITEMS_IN_MATRIX
1472 					&& archive->FindRect("_i_frames", i, &bounds) == B_OK)
1473 					AddItem(item, bounds);
1474 				else
1475 					AddItem(item);
1476 			}
1477 		}
1478 	}
1479 }
1480 
1481 
1482 bool
1483 BMenu::_Show(bool selectFirstItem, bool keyDown)
1484 {
1485 	// See if the supermenu has a cached menuwindow,
1486 	// and use that one if possible.
1487 	BMenuWindow* window = NULL;
1488 	bool ourWindow = false;
1489 	if (fSuper != NULL) {
1490 		fSuperbounds = fSuper->ConvertToScreen(fSuper->Bounds());
1491 		window = fSuper->_MenuWindow();
1492 	}
1493 
1494 	// Otherwise, create a new one
1495 	// This happens for "stand alone" BPopUpMenus
1496 	// (i.e. not within a BMenuField)
1497 	if (window == NULL) {
1498 		// Menu windows get the BMenu's handler name
1499 		window = new (nothrow) BMenuWindow(Name());
1500 		ourWindow = true;
1501 	}
1502 
1503 	if (window == NULL)
1504 		return false;
1505 
1506 	if (window->Lock()) {
1507 		bool addAborted = false;
1508 		if (keyDown)
1509 			addAborted = _AddDynamicItems(keyDown);
1510 
1511 		if (addAborted) {
1512 			if (ourWindow)
1513 				window->Quit();
1514 			else
1515 				window->Unlock();
1516 			return false;
1517 		}
1518 		fAttachAborted = false;
1519 
1520 		window->AttachMenu(this);
1521 
1522 		if (ItemAt(0) != NULL) {
1523 			float width, height;
1524 			ItemAt(0)->GetContentSize(&width, &height);
1525 
1526 			window->SetSmallStep(ceilf(height));
1527 		}
1528 
1529 		// Menu didn't have the time to add its items: aborting...
1530 		if (fAttachAborted) {
1531 			window->DetachMenu();
1532 			// TODO: Probably not needed, we can just let _hide() quit the
1533 			// window.
1534 			if (ourWindow)
1535 				window->Quit();
1536 			else
1537 				window->Unlock();
1538 			return false;
1539 		}
1540 
1541 		_UpdateWindowViewSize(true);
1542 		window->Show();
1543 
1544 		if (selectFirstItem)
1545 			_SelectItem(ItemAt(0), false);
1546 
1547 		window->Unlock();
1548 	}
1549 
1550 	return true;
1551 }
1552 
1553 
1554 void
1555 BMenu::_Hide()
1556 {
1557 	BMenuWindow* window = dynamic_cast<BMenuWindow*>(Window());
1558 	if (window == NULL || !window->Lock())
1559 		return;
1560 
1561 	if (fSelected != NULL)
1562 		_SelectItem(NULL);
1563 
1564 	window->Hide();
1565 	window->DetachMenu();
1566 		// we don't want to be deleted when the window is removed
1567 
1568 #if USE_CACHED_MENUWINDOW
1569 	if (fSuper != NULL)
1570 		window->Unlock();
1571 	else
1572 #endif
1573 		window->Quit();
1574 		// it's our window, quit it
1575 
1576 
1577 	// Delete the menu window used by our submenus
1578 	_DeleteMenuWindow();
1579 }
1580 
1581 
1582 // #pragma mark - mouse tracking
1583 
1584 
1585 const static bigtime_t kOpenSubmenuDelay = 225000;
1586 const static bigtime_t kNavigationAreaTimeout = 1000000;
1587 
1588 
1589 BMenuItem*
1590 BMenu::_Track(int* action, long start)
1591 {
1592 	// TODO: cleanup
1593 	BMenuItem* item = NULL;
1594 	BRect navAreaRectAbove;
1595 	BRect navAreaRectBelow;
1596 	bigtime_t selectedTime = system_time();
1597 	bigtime_t navigationAreaTime = 0;
1598 
1599 	fState = MENU_STATE_TRACKING;
1600 	// we will use this for keyboard selection:
1601 	fChosenItem = NULL;
1602 
1603 	BPoint location;
1604 	uint32 buttons = 0;
1605 	if (LockLooper()) {
1606 		GetMouse(&location, &buttons);
1607 		UnlockLooper();
1608 	}
1609 
1610 	bool releasedOnce = buttons == 0;
1611 	while (fState != MENU_STATE_CLOSED) {
1612 		if (_CustomTrackingWantsToQuit())
1613 			break;
1614 
1615 		if (!LockLooper())
1616 			break;
1617 
1618 		BMenuWindow* window = static_cast<BMenuWindow*>(Window());
1619 		BPoint screenLocation = ConvertToScreen(location);
1620 		if (window->CheckForScrolling(screenLocation)) {
1621 			UnlockLooper();
1622 			continue;
1623 		}
1624 
1625 		// The order of the checks is important
1626 		// to be able to handle overlapping menus:
1627 		// first we check if mouse is inside a submenu,
1628 		// then if the mouse is inside this menu,
1629 		// then if it's over a super menu.
1630 		bool overSub = _OverSubmenu(fSelected, screenLocation);
1631 		item = _HitTestItems(location, B_ORIGIN);
1632 		if (overSub || fState == MENU_STATE_KEY_TO_SUBMENU) {
1633 			if (fState == MENU_STATE_TRACKING) {
1634 				// not if from R.Arrow
1635 				fState = MENU_STATE_TRACKING_SUBMENU;
1636 			}
1637 			navAreaRectAbove = BRect();
1638 			navAreaRectBelow = BRect();
1639 
1640 			// Since the submenu has its own looper,
1641 			// we can unlock ours. Doing so also make sure
1642 			// that our window gets any update message to
1643 			// redraw itself
1644 			UnlockLooper();
1645 			int submenuAction = MENU_STATE_TRACKING;
1646 			BMenu* submenu = fSelected->Submenu();
1647 			submenu->_SetStickyMode(_IsStickyMode());
1648 
1649 			// The following call blocks until the submenu
1650 			// gives control back to us, either because the mouse
1651 			// pointer goes out of the submenu's bounds, or because
1652 			// the user closes the menu
1653 			BMenuItem* submenuItem = submenu->_Track(&submenuAction);
1654 			if (submenuAction == MENU_STATE_CLOSED) {
1655 				item = submenuItem;
1656 				fState = MENU_STATE_CLOSED;
1657 			} else if (submenuAction == MENU_STATE_KEY_LEAVE_SUBMENU) {
1658 				if (LockLooper()) {
1659 					BMenuItem *temp = fSelected;
1660 					// close the submenu:
1661 					_SelectItem(NULL);
1662 					// but reselect the item itself for user:
1663 					_SelectItem(temp, false);
1664 					UnlockLooper();
1665 				}
1666 				// cancel  key-nav state
1667 				fState = MENU_STATE_TRACKING;
1668 			} else
1669 				fState = MENU_STATE_TRACKING;
1670 			if (!LockLooper())
1671 				break;
1672 		} else if (item != NULL) {
1673 			_UpdateStateOpenSelect(item, location, navAreaRectAbove,
1674 				navAreaRectBelow, selectedTime, navigationAreaTime);
1675 			if (!releasedOnce)
1676 				releasedOnce = true;
1677 		} else if (_OverSuper(screenLocation) && fSuper->fState != MENU_STATE_KEY_TO_SUBMENU) {
1678 			fState = MENU_STATE_TRACKING;
1679 			UnlockLooper();
1680 			break;
1681 		} else if (fState == MENU_STATE_KEY_LEAVE_SUBMENU) {
1682 			UnlockLooper();
1683 			break;
1684 		} else if (fSuper == NULL || fSuper->fState != MENU_STATE_KEY_TO_SUBMENU) {
1685 			// Mouse pointer outside menu:
1686 			// If there's no other submenu opened,
1687 			// deselect the current selected item
1688 			if (fSelected != NULL
1689 				&& (fSelected->Submenu() == NULL
1690 					|| fSelected->Submenu()->Window() == NULL)) {
1691 				_SelectItem(NULL);
1692 				fState = MENU_STATE_TRACKING;
1693 			}
1694 
1695 			if (fSuper != NULL) {
1696 				// Give supermenu the chance to continue tracking
1697 				*action = fState;
1698 				UnlockLooper();
1699 				return NULL;
1700 			}
1701 		}
1702 
1703 		UnlockLooper();
1704 
1705 		if (releasedOnce)
1706 			_UpdateStateClose(item, location, buttons);
1707 
1708 		if (fState != MENU_STATE_CLOSED) {
1709 			bigtime_t snoozeAmount = 50000;
1710 
1711 			BPoint newLocation = location;
1712 			uint32 newButtons = buttons;
1713 
1714 			// If user doesn't move the mouse, loop here,
1715 			// so we don't interfere with keyboard menu navigation
1716 			do {
1717 				snooze(snoozeAmount);
1718 				if (!LockLooper())
1719 					break;
1720 				GetMouse(&newLocation, &newButtons, true);
1721 				UnlockLooper();
1722 			} while (newLocation == location && newButtons == buttons
1723 				&& !(item && item->Submenu() != NULL)
1724 				&& fState == MENU_STATE_TRACKING);
1725 
1726 			if (newLocation != location || newButtons != buttons) {
1727 				if (!releasedOnce && newButtons == 0 && buttons != 0)
1728 					releasedOnce = true;
1729 				location = newLocation;
1730 				buttons = newButtons;
1731 			}
1732 
1733 			if (releasedOnce)
1734 				_UpdateStateClose(item, location, buttons);
1735 		}
1736 	}
1737 
1738 	if (action != NULL)
1739 		*action = fState;
1740 
1741 	// keyboard Enter will set this
1742 	if (fChosenItem != NULL)
1743 		item = fChosenItem;
1744 	else if (fSelected == NULL)
1745 		// needed to cover (rare) mouse/ESC combination
1746 		item = NULL;
1747 
1748 	if (fSelected != NULL && LockLooper()) {
1749 		_SelectItem(NULL);
1750 		UnlockLooper();
1751 	}
1752 
1753 	// delete the menu window recycled for all the child menus
1754 	_DeleteMenuWindow();
1755 
1756 	return item;
1757 }
1758 
1759 
1760 void
1761 BMenu::_UpdateNavigationArea(BPoint position, BRect& navAreaRectAbove,
1762 	BRect& navAreaRectBelow)
1763 {
1764 #define NAV_AREA_THRESHOLD    8
1765 
1766 	// The navigation area is a region in which mouse-overs won't select
1767 	// the item under the cursor. This makes it easier to navigate to
1768 	// submenus, as the cursor can be moved to submenu items directly instead
1769 	// of having to move it horizontally into the submenu first. The concept
1770 	// is illustrated below:
1771 	//
1772 	// +-------+----+---------+
1773 	// |       |   /|         |
1774 	// |       |  /*|         |
1775 	// |[2]--> | /**|         |
1776 	// |       |/[4]|         |
1777 	// |------------|         |
1778 	// |    [1]     |   [6]   |
1779 	// |------------|         |
1780 	// |       |\[5]|         |
1781 	// |[3]--> | \**|         |
1782 	// |       |  \*|         |
1783 	// |       |   \|         |
1784 	// |       +----|---------+
1785 	// |            |
1786 	// +------------+
1787 	//
1788 	// [1] Selected item, cursor position ('position')
1789 	// [2] Upper navigation area rectangle ('navAreaRectAbove')
1790 	// [3] Lower navigation area rectangle ('navAreaRectBelow')
1791 	// [4] Upper navigation area
1792 	// [5] Lower navigation area
1793 	// [6] Submenu
1794 	//
1795 	// The rectangles are used to calculate if the cursor is in the actual
1796 	// navigation area (see _UpdateStateOpenSelect()).
1797 
1798 	if (fSelected == NULL)
1799 		return;
1800 
1801 	BMenu* submenu = fSelected->Submenu();
1802 
1803 	if (submenu != NULL) {
1804 		BRect menuBounds = ConvertToScreen(Bounds());
1805 
1806 		fSelected->Submenu()->LockLooper();
1807 		BRect submenuBounds = fSelected->Submenu()->ConvertToScreen(
1808 			fSelected->Submenu()->Bounds());
1809 		fSelected->Submenu()->UnlockLooper();
1810 
1811 		if (menuBounds.left < submenuBounds.left) {
1812 			navAreaRectAbove.Set(position.x + NAV_AREA_THRESHOLD,
1813 				submenuBounds.top, menuBounds.right,
1814 				position.y);
1815 			navAreaRectBelow.Set(position.x + NAV_AREA_THRESHOLD,
1816 				position.y, menuBounds.right,
1817 				submenuBounds.bottom);
1818 		} else {
1819 			navAreaRectAbove.Set(menuBounds.left,
1820 				submenuBounds.top, position.x - NAV_AREA_THRESHOLD,
1821 				position.y);
1822 			navAreaRectBelow.Set(menuBounds.left,
1823 				position.y, position.x - NAV_AREA_THRESHOLD,
1824 				submenuBounds.bottom);
1825 		}
1826 	} else {
1827 		navAreaRectAbove = BRect();
1828 		navAreaRectBelow = BRect();
1829 	}
1830 }
1831 
1832 
1833 void
1834 BMenu::_UpdateStateOpenSelect(BMenuItem* item, BPoint position,
1835 	BRect& navAreaRectAbove, BRect& navAreaRectBelow, bigtime_t& selectedTime,
1836 	bigtime_t& navigationAreaTime)
1837 {
1838 	if (fState == MENU_STATE_CLOSED)
1839 		return;
1840 
1841 	if (item != fSelected) {
1842 		if (navigationAreaTime == 0)
1843 			navigationAreaTime = system_time();
1844 
1845 		position = ConvertToScreen(position);
1846 
1847 		bool inNavAreaRectAbove = navAreaRectAbove.Contains(position);
1848 		bool inNavAreaRectBelow = navAreaRectBelow.Contains(position);
1849 
1850 		if (fSelected == NULL
1851 			|| (!inNavAreaRectAbove && !inNavAreaRectBelow)) {
1852 			_SelectItem(item, false);
1853 			navAreaRectAbove = BRect();
1854 			navAreaRectBelow = BRect();
1855 			selectedTime = system_time();
1856 			navigationAreaTime = 0;
1857 			return;
1858 		}
1859 
1860 		BRect menuBounds = ConvertToScreen(Bounds());
1861 
1862 		fSelected->Submenu()->LockLooper();
1863 		BRect submenuBounds = fSelected->Submenu()->ConvertToScreen(
1864 			fSelected->Submenu()->Bounds());
1865 		fSelected->Submenu()->UnlockLooper();
1866 
1867 		float xOffset;
1868 
1869 		// navAreaRectAbove and navAreaRectBelow have the same X
1870 		// position and width, so it doesn't matter which one we use to
1871 		// calculate the X offset
1872 		if (menuBounds.left < submenuBounds.left)
1873 			xOffset = position.x - navAreaRectAbove.left;
1874 		else
1875 			xOffset = navAreaRectAbove.right - position.x;
1876 
1877 		bool inNavArea;
1878 
1879 		if (inNavAreaRectAbove) {
1880 			float yOffset = navAreaRectAbove.bottom - position.y;
1881 			float ratio = navAreaRectAbove.Width() / navAreaRectAbove.Height();
1882 
1883 			inNavArea = yOffset <= xOffset / ratio;
1884 		} else {
1885 			float yOffset = navAreaRectBelow.bottom - position.y;
1886 			float ratio = navAreaRectBelow.Width() / navAreaRectBelow.Height();
1887 
1888 			inNavArea = yOffset >= (navAreaRectBelow.Height() - xOffset
1889 				/ ratio);
1890 		}
1891 
1892 		bigtime_t systime = system_time();
1893 
1894 		if (!inNavArea || (navigationAreaTime > 0 && systime -
1895 			navigationAreaTime > kNavigationAreaTimeout)) {
1896 			// Don't delay opening of submenu if the user had
1897 			// to wait for the navigation area timeout anyway
1898 			_SelectItem(item, inNavArea);
1899 
1900 			if (inNavArea) {
1901 				_UpdateNavigationArea(position, navAreaRectAbove,
1902 					navAreaRectBelow);
1903 			} else {
1904 				navAreaRectAbove = BRect();
1905 				navAreaRectBelow = BRect();
1906 			}
1907 
1908 			selectedTime = system_time();
1909 			navigationAreaTime = 0;
1910 		}
1911 	} else if (fSelected->Submenu() != NULL &&
1912 		system_time() - selectedTime > kOpenSubmenuDelay) {
1913 		_SelectItem(fSelected, true);
1914 
1915 		if (!navAreaRectAbove.IsValid() && !navAreaRectBelow.IsValid()) {
1916 			position = ConvertToScreen(position);
1917 			_UpdateNavigationArea(position, navAreaRectAbove,
1918 				navAreaRectBelow);
1919 		}
1920 	}
1921 
1922 	if (fState != MENU_STATE_TRACKING)
1923 		fState = MENU_STATE_TRACKING;
1924 }
1925 
1926 
1927 void
1928 BMenu::_UpdateStateClose(BMenuItem* item, const BPoint& where,
1929 	const uint32& buttons)
1930 {
1931 	if (fState == MENU_STATE_CLOSED)
1932 		return;
1933 
1934 	if (buttons != 0 && _IsStickyMode()) {
1935 		if (item == NULL) {
1936 			if (item != fSelected) {
1937 				LockLooper();
1938 				_SelectItem(item, false);
1939 				UnlockLooper();
1940 			}
1941 			fState = MENU_STATE_CLOSED;
1942 		} else
1943 			_SetStickyMode(false);
1944 	} else if (buttons == 0 && !_IsStickyMode()) {
1945 		if (fExtraRect != NULL && fExtraRect->Contains(where)) {
1946 			_SetStickyMode(true);
1947 			fExtraRect = NULL;
1948 				// Setting this to NULL will prevent this code
1949 				// to be executed next time
1950 		} else {
1951 			if (item != fSelected) {
1952 				LockLooper();
1953 				_SelectItem(item, false);
1954 				UnlockLooper();
1955 			}
1956 			fState = MENU_STATE_CLOSED;
1957 		}
1958 	}
1959 }
1960 
1961 
1962 // #pragma mark -
1963 
1964 
1965 bool
1966 BMenu::_AddItem(BMenuItem* item, int32 index)
1967 {
1968 	ASSERT(item != NULL);
1969 	if (index < 0 || index > fItems.CountItems())
1970 		return false;
1971 
1972 	if (item->IsMarked())
1973 		_ItemMarked(item);
1974 
1975 	if (!fItems.AddItem(item, index))
1976 		return false;
1977 
1978 	// install the item on the supermenu's window
1979 	// or onto our window, if we are a root menu
1980 	BWindow* window = NULL;
1981 	if (Superitem() != NULL)
1982 		window = Superitem()->fWindow;
1983 	else
1984 		window = Window();
1985 	if (window != NULL)
1986 		item->Install(window);
1987 
1988 	item->SetSuper(this);
1989 	return true;
1990 }
1991 
1992 
1993 bool
1994 BMenu::_RemoveItems(int32 index, int32 count, BMenuItem* item,
1995 	bool deleteItems)
1996 {
1997 	bool success = false;
1998 	bool invalidateLayout = false;
1999 
2000 	bool locked = LockLooper();
2001 	BWindow* window = Window();
2002 
2003 	// The plan is simple: If we're given a BMenuItem directly, we use it
2004 	// and ignore index and count. Otherwise, we use them instead.
2005 	if (item != NULL) {
2006 		if (fItems.RemoveItem(item)) {
2007 			if (item == fSelected && window != NULL)
2008 				_SelectItem(NULL);
2009 			item->Uninstall();
2010 			item->SetSuper(NULL);
2011 			if (deleteItems)
2012 				delete item;
2013 			success = invalidateLayout = true;
2014 		}
2015 	} else {
2016 		// We iterate backwards because it's simpler
2017 		int32 i = min_c(index + count - 1, fItems.CountItems() - 1);
2018 		// NOTE: the range check for "index" is done after
2019 		// calculating the last index to be removed, so
2020 		// that the range is not "shifted" unintentionally
2021 		index = max_c(0, index);
2022 		for (; i >= index; i--) {
2023 			item = static_cast<BMenuItem*>(fItems.ItemAt(i));
2024 			if (item != NULL) {
2025 				if (fItems.RemoveItem(item)) {
2026 					if (item == fSelected && window != NULL)
2027 						_SelectItem(NULL);
2028 					item->Uninstall();
2029 					item->SetSuper(NULL);
2030 					if (deleteItems)
2031 						delete item;
2032 					success = true;
2033 					invalidateLayout = true;
2034 				} else {
2035 					// operation not entirely successful
2036 					success = false;
2037 					break;
2038 				}
2039 			}
2040 		}
2041 	}
2042 
2043 	if (invalidateLayout) {
2044 		InvalidateLayout();
2045 		if (locked && window != NULL) {
2046 			_LayoutItems(0);
2047 			_UpdateWindowViewSize(false);
2048 			Invalidate();
2049 		}
2050 	}
2051 
2052 	if (locked)
2053 		UnlockLooper();
2054 
2055 	return success;
2056 }
2057 
2058 
2059 bool
2060 BMenu::_RelayoutIfNeeded()
2061 {
2062 	if (!fUseCachedMenuLayout) {
2063 		fUseCachedMenuLayout = true;
2064 		_CacheFontInfo();
2065 		_LayoutItems(0);
2066 		return true;
2067 	}
2068 	return false;
2069 }
2070 
2071 
2072 void
2073 BMenu::_LayoutItems(int32 index)
2074 {
2075 	_CalcTriggers();
2076 
2077 	float width, height;
2078 	_ComputeLayout(index, fResizeToFit, true, &width, &height);
2079 
2080 	if (fResizeToFit)
2081 		ResizeTo(width, height);
2082 }
2083 
2084 
2085 BSize
2086 BMenu::_ValidatePreferredSize()
2087 {
2088 	if (!fLayoutData->preferred.IsWidthSet() || ResizingMode()
2089 			!= fLayoutData->lastResizingMode) {
2090 		_ComputeLayout(0, true, false, NULL, NULL);
2091 		ResetLayoutInvalidation();
2092 	}
2093 
2094 	return fLayoutData->preferred;
2095 }
2096 
2097 
2098 void
2099 BMenu::_ComputeLayout(int32 index, bool bestFit, bool moveItems,
2100 	float* _width, float* _height)
2101 {
2102 	// TODO: Take "bestFit", "moveItems", "index" into account,
2103 	// Recalculate only the needed items,
2104 	// not the whole layout every time
2105 
2106 	fLayoutData->lastResizingMode = ResizingMode();
2107 
2108 	BRect frame;
2109 
2110 	switch (fLayout) {
2111 		case B_ITEMS_IN_COLUMN:
2112 			_ComputeColumnLayout(index, bestFit, moveItems, frame);
2113 			break;
2114 
2115 		case B_ITEMS_IN_ROW:
2116 			_ComputeRowLayout(index, bestFit, moveItems, frame);
2117 			break;
2118 
2119 		case B_ITEMS_IN_MATRIX:
2120 			_ComputeMatrixLayout(frame);
2121 			break;
2122 
2123 		default:
2124 			break;
2125 	}
2126 
2127 	// change width depending on resize mode
2128 	BSize size;
2129 	if ((ResizingMode() & B_FOLLOW_LEFT_RIGHT) == B_FOLLOW_LEFT_RIGHT) {
2130 		if (Parent())
2131 			size.width = Parent()->Frame().Width() + 1;
2132 		else if (Window())
2133 			size.width = Window()->Frame().Width() + 1;
2134 		else
2135 			size.width = Bounds().Width();
2136 	} else
2137 		size.width = frame.Width();
2138 
2139 	size.height = frame.Height();
2140 
2141 	if (_width)
2142 		*_width = size.width;
2143 
2144 	if (_height)
2145 		*_height = size.height;
2146 
2147 	if (bestFit)
2148 		fLayoutData->preferred = size;
2149 
2150 	if (moveItems)
2151 		fUseCachedMenuLayout = true;
2152 }
2153 
2154 
2155 void
2156 BMenu::_ComputeColumnLayout(int32 index, bool bestFit, bool moveItems,
2157 	BRect& frame)
2158 {
2159 	BFont font;
2160 	GetFont(&font);
2161 	bool command = false;
2162 	bool control = false;
2163 	bool shift = false;
2164 	bool option = false;
2165 	if (index > 0)
2166 		frame = ItemAt(index - 1)->Frame();
2167 	else
2168 		frame.Set(0, 0, 0, -1);
2169 
2170 	for (; index < fItems.CountItems(); index++) {
2171 		BMenuItem* item = ItemAt(index);
2172 
2173 		float width, height;
2174 		item->GetContentSize(&width, &height);
2175 
2176 		if (item->fModifiers && item->fShortcutChar) {
2177 			width += font.Size();
2178 			if (item->fModifiers & B_COMMAND_KEY)
2179 				command = true;
2180 			if (item->fModifiers & B_CONTROL_KEY)
2181 				control = true;
2182 			if (item->fModifiers & B_SHIFT_KEY)
2183 				shift = true;
2184 			if (item->fModifiers & B_OPTION_KEY)
2185 				option = true;
2186 		}
2187 
2188 		item->fBounds.left = 0.0f;
2189 		item->fBounds.top = frame.bottom + 1.0f;
2190 		item->fBounds.bottom = item->fBounds.top + height + fPad.top
2191 			+ fPad.bottom;
2192 
2193 		if (item->fSubmenu != NULL)
2194 			width += item->Frame().Height();
2195 
2196 		frame.right = max_c(frame.right, width + fPad.left + fPad.right);
2197 		frame.bottom = item->fBounds.bottom;
2198 	}
2199 
2200 	if (command)
2201 		frame.right += BPrivate::MenuPrivate::MenuItemCommand()->Bounds().Width() + 1;
2202 	if (control)
2203 		frame.right += BPrivate::MenuPrivate::MenuItemControl()->Bounds().Width() + 1;
2204 	if (option)
2205 		frame.right += BPrivate::MenuPrivate::MenuItemOption()->Bounds().Width() + 1;
2206 	if (shift)
2207 		frame.right += BPrivate::MenuPrivate::MenuItemShift()->Bounds().Width() + 1;
2208 
2209 	if (fMaxContentWidth > 0)
2210 		frame.right = min_c(frame.right, fMaxContentWidth);
2211 
2212 	if (moveItems) {
2213 		for (int32 i = 0; i < fItems.CountItems(); i++)
2214 			ItemAt(i)->fBounds.right = frame.right;
2215 	}
2216 
2217 	frame.top = 0;
2218 	frame.right = ceilf(frame.right);
2219 }
2220 
2221 
2222 void
2223 BMenu::_ComputeRowLayout(int32 index, bool bestFit, bool moveItems,
2224 	BRect& frame)
2225 {
2226 	font_height fh;
2227 	GetFontHeight(&fh);
2228 	frame.Set(0.0f, 0.0f, 0.0f, ceilf(fh.ascent + fh.descent + fPad.top
2229 		+ fPad.bottom));
2230 
2231 	for (int32 i = 0; i < fItems.CountItems(); i++) {
2232 		BMenuItem* item = ItemAt(i);
2233 
2234 		float width, height;
2235 		item->GetContentSize(&width, &height);
2236 
2237 		item->fBounds.left = frame.right;
2238 		item->fBounds.top = 0.0f;
2239 		item->fBounds.right = item->fBounds.left + width + fPad.left
2240 			+ fPad.right;
2241 
2242 		frame.right = item->Frame().right + 1.0f;
2243 		frame.bottom = max_c(frame.bottom, height + fPad.top + fPad.bottom);
2244 	}
2245 
2246 	if (moveItems) {
2247 		for (int32 i = 0; i < fItems.CountItems(); i++)
2248 			ItemAt(i)->fBounds.bottom = frame.bottom;
2249 	}
2250 
2251 	if (bestFit)
2252 		frame.right = ceilf(frame.right);
2253 	else
2254 		frame.right = Bounds().right;
2255 }
2256 
2257 
2258 void
2259 BMenu::_ComputeMatrixLayout(BRect &frame)
2260 {
2261 	frame.Set(0, 0, 0, 0);
2262 	for (int32 i = 0; i < CountItems(); i++) {
2263 		BMenuItem* item = ItemAt(i);
2264 		if (item != NULL) {
2265 			frame.left = min_c(frame.left, item->Frame().left);
2266 			frame.right = max_c(frame.right, item->Frame().right);
2267 			frame.top = min_c(frame.top, item->Frame().top);
2268 			frame.bottom = max_c(frame.bottom, item->Frame().bottom);
2269 		}
2270 	}
2271 }
2272 
2273 
2274 // Assumes the SuperMenu to be locked (due to calling ConvertToScreen())
2275 BPoint
2276 BMenu::ScreenLocation()
2277 {
2278 	BMenu* superMenu = Supermenu();
2279 	BMenuItem* superItem = Superitem();
2280 
2281 	if (superMenu == NULL || superItem == NULL) {
2282 		debugger("BMenu can't determine where to draw."
2283 			"Override BMenu::ScreenLocation() to determine location.");
2284 	}
2285 
2286 	BPoint point;
2287 	if (superMenu->Layout() == B_ITEMS_IN_COLUMN)
2288 		point = superItem->Frame().RightTop() + BPoint(1.0f, 1.0f);
2289 	else
2290 		point = superItem->Frame().LeftBottom() + BPoint(1.0f, 1.0f);
2291 
2292 	superMenu->ConvertToScreen(&point);
2293 
2294 	return point;
2295 }
2296 
2297 
2298 BRect
2299 BMenu::_CalcFrame(BPoint where, bool* scrollOn)
2300 {
2301 	// TODO: Improve me
2302 	BRect bounds = Bounds();
2303 	BRect frame = bounds.OffsetToCopy(where);
2304 
2305 	BScreen screen(Window());
2306 	BRect screenFrame = screen.Frame();
2307 
2308 	BMenu* superMenu = Supermenu();
2309 	BMenuItem* superItem = Superitem();
2310 
2311 	bool scroll = false;
2312 
2313 	// TODO: Horrible hack:
2314 	// When added to a BMenuField, a BPopUpMenu is the child of
2315 	// a _BMCMenuBar_ to "fake" the menu hierarchy
2316 	if (superMenu == NULL || superItem == NULL
2317 		|| dynamic_cast<_BMCMenuBar_*>(superMenu) != NULL) {
2318 		// just move the window on screen
2319 
2320 		if (frame.bottom > screenFrame.bottom)
2321 			frame.OffsetBy(0, screenFrame.bottom - frame.bottom);
2322 		else if (frame.top < screenFrame.top)
2323 			frame.OffsetBy(0, -frame.top);
2324 
2325 		if (frame.right > screenFrame.right)
2326 			frame.OffsetBy(screenFrame.right - frame.right, 0);
2327 		else if (frame.left < screenFrame.left)
2328 			frame.OffsetBy(-frame.left, 0);
2329 	} else if (superMenu->Layout() == B_ITEMS_IN_COLUMN) {
2330 		if (frame.right > screenFrame.right)
2331 			frame.OffsetBy(-superItem->Frame().Width() - frame.Width() - 2, 0);
2332 
2333 		if (frame.left < 0)
2334 			frame.OffsetBy(-frame.left + 6, 0);
2335 
2336 		if (frame.bottom > screenFrame.bottom)
2337 			frame.OffsetBy(0, screenFrame.bottom - frame.bottom);
2338 	} else {
2339 		if (frame.bottom > screenFrame.bottom) {
2340 			if (scrollOn != NULL && superMenu != NULL
2341 				&& dynamic_cast<BMenuBar*>(superMenu) != NULL
2342 				&& frame.top < (screenFrame.bottom - 80)) {
2343 				scroll = true;
2344 			} else {
2345 				frame.OffsetBy(0, -superItem->Frame().Height()
2346 					- frame.Height() - 3);
2347 			}
2348 		}
2349 
2350 		if (frame.right > screenFrame.right)
2351 			frame.OffsetBy(screenFrame.right - frame.right, 0);
2352 	}
2353 
2354 	if (!scroll) {
2355 		// basically, if this returns false, it means
2356 		// that the menu frame won't fit completely inside the screen
2357 		// TODO: Scrolling will currently only work up/down,
2358 		// not left/right
2359 		scroll = screenFrame.Height() < frame.Height();
2360 	}
2361 
2362 	if (scrollOn != NULL)
2363 		*scrollOn = scroll;
2364 
2365 	return frame;
2366 }
2367 
2368 
2369 void
2370 BMenu::_DrawItems(BRect updateRect)
2371 {
2372 	int32 itemCount = fItems.CountItems();
2373 	for (int32 i = 0; i < itemCount; i++) {
2374 		BMenuItem* item = ItemAt(i);
2375 		if (item->Frame().Intersects(updateRect))
2376 			item->Draw();
2377 	}
2378 }
2379 
2380 
2381 int
2382 BMenu::_State(BMenuItem** item) const
2383 {
2384 	if (fState == MENU_STATE_TRACKING || fState == MENU_STATE_CLOSED)
2385 		return fState;
2386 
2387 	if (fSelected != NULL && fSelected->Submenu() != NULL)
2388 		return fSelected->Submenu()->_State(item);
2389 
2390 	return fState;
2391 }
2392 
2393 
2394 void
2395 BMenu::_InvokeItem(BMenuItem* item, bool now)
2396 {
2397 	if (!item->IsEnabled())
2398 		return;
2399 
2400 	// Do the "selected" animation
2401 	// TODO: Doesn't work. This is supposed to highlight
2402 	// and dehighlight the item, works on beos but not on haiku.
2403 	if (!item->Submenu() && LockLooper()) {
2404 		snooze(50000);
2405 		item->Select(true);
2406 		Window()->UpdateIfNeeded();
2407 		snooze(50000);
2408 		item->Select(false);
2409 		Window()->UpdateIfNeeded();
2410 		snooze(50000);
2411 		item->Select(true);
2412 		Window()->UpdateIfNeeded();
2413 		snooze(50000);
2414 		item->Select(false);
2415 		Window()->UpdateIfNeeded();
2416 		UnlockLooper();
2417 	}
2418 
2419 	// Lock the root menu window before calling BMenuItem::Invoke()
2420 	BMenu* parent = this;
2421 	BMenu* rootMenu = NULL;
2422 	do {
2423 		rootMenu = parent;
2424 		parent = rootMenu->Supermenu();
2425 	} while (parent != NULL);
2426 
2427 	if (rootMenu->LockLooper()) {
2428 		item->Invoke();
2429 		rootMenu->UnlockLooper();
2430 	}
2431 }
2432 
2433 
2434 bool
2435 BMenu::_OverSuper(BPoint location)
2436 {
2437 	if (!Supermenu())
2438 		return false;
2439 
2440 	return fSuperbounds.Contains(location);
2441 }
2442 
2443 
2444 bool
2445 BMenu::_OverSubmenu(BMenuItem* item, BPoint loc)
2446 {
2447 	if (item == NULL)
2448 		return false;
2449 
2450 	BMenu* subMenu = item->Submenu();
2451 	if (subMenu == NULL || subMenu->Window() == NULL)
2452 		return false;
2453 
2454 	// we assume that loc is in screen coords {
2455 	if (subMenu->Window()->Frame().Contains(loc))
2456 		return true;
2457 
2458 	return subMenu->_OverSubmenu(subMenu->fSelected, loc);
2459 }
2460 
2461 
2462 BMenuWindow*
2463 BMenu::_MenuWindow()
2464 {
2465 #if USE_CACHED_MENUWINDOW
2466 	if (fCachedMenuWindow == NULL) {
2467 		char windowName[64];
2468 		snprintf(windowName, 64, "%s cached menu", Name());
2469 		fCachedMenuWindow = new (nothrow) BMenuWindow(windowName);
2470 	}
2471 #endif
2472 	return fCachedMenuWindow;
2473 }
2474 
2475 
2476 void
2477 BMenu::_DeleteMenuWindow()
2478 {
2479 	if (fCachedMenuWindow != NULL) {
2480 		fCachedMenuWindow->Lock();
2481 		fCachedMenuWindow->Quit();
2482 		fCachedMenuWindow = NULL;
2483 	}
2484 }
2485 
2486 
2487 BMenuItem*
2488 BMenu::_HitTestItems(BPoint where, BPoint slop) const
2489 {
2490 	// TODO: Take "slop" into account ?
2491 
2492 	// if the point doesn't lie within the menu's
2493 	// bounds, bail out immediately
2494 	if (!Bounds().Contains(where))
2495 		return NULL;
2496 
2497 	int32 itemCount = CountItems();
2498 	for (int32 i = 0; i < itemCount; i++) {
2499 		BMenuItem* item = ItemAt(i);
2500 		if (item->IsEnabled() && item->Frame().Contains(where))
2501 			return item;
2502 	}
2503 
2504 	return NULL;
2505 }
2506 
2507 
2508 BRect
2509 BMenu::_Superbounds() const
2510 {
2511 	return fSuperbounds;
2512 }
2513 
2514 
2515 void
2516 BMenu::_CacheFontInfo()
2517 {
2518 	font_height fh;
2519 	GetFontHeight(&fh);
2520 	fAscent = fh.ascent;
2521 	fDescent = fh.descent;
2522 	fFontHeight = ceilf(fh.ascent + fh.descent + fh.leading);
2523 }
2524 
2525 
2526 void
2527 BMenu::_ItemMarked(BMenuItem* item)
2528 {
2529 	if (IsRadioMode()) {
2530 		for (int32 i = 0; i < CountItems(); i++) {
2531 			if (ItemAt(i) != item)
2532 				ItemAt(i)->SetMarked(false);
2533 		}
2534 		InvalidateLayout();
2535 	}
2536 
2537 	if (IsLabelFromMarked() && Superitem())
2538 		Superitem()->SetLabel(item->Label());
2539 }
2540 
2541 
2542 void
2543 BMenu::_Install(BWindow* target)
2544 {
2545 	for (int32 i = 0; i < CountItems(); i++)
2546 		ItemAt(i)->Install(target);
2547 }
2548 
2549 
2550 void
2551 BMenu::_Uninstall()
2552 {
2553 	for (int32 i = 0; i < CountItems(); i++)
2554 		ItemAt(i)->Uninstall();
2555 }
2556 
2557 
2558 void
2559 BMenu::_SelectItem(BMenuItem* menuItem, bool showSubmenu,
2560 	bool selectFirstItem, bool keyDown)
2561 {
2562 	// Avoid deselecting and then reselecting the same item
2563 	// which would cause flickering
2564 	if (menuItem != fSelected) {
2565 		if (fSelected != NULL) {
2566 			fSelected->Select(false);
2567 			BMenu* subMenu = fSelected->Submenu();
2568 			if (subMenu != NULL && subMenu->Window() != NULL)
2569 				subMenu->_Hide();
2570 		}
2571 
2572 		fSelected = menuItem;
2573 		if (fSelected != NULL)
2574 			fSelected->Select(true);
2575 	}
2576 
2577 	if (fSelected != NULL && showSubmenu) {
2578 		BMenu* subMenu = fSelected->Submenu();
2579 		if (subMenu != NULL && subMenu->Window() == NULL) {
2580 			if (!subMenu->_Show(selectFirstItem, keyDown)) {
2581 				// something went wrong, deselect the item
2582 				fSelected->Select(false);
2583 				fSelected = NULL;
2584 			}
2585 		}
2586 	}
2587 }
2588 
2589 
2590 bool
2591 BMenu::_SelectNextItem(BMenuItem* item, bool forward)
2592 {
2593 	if (CountItems() == 0) // cannot select next item in an empty menu
2594 		return false;
2595 
2596 	BMenuItem* nextItem = _NextItem(item, forward);
2597 	if (nextItem == NULL)
2598 		return false;
2599 
2600 	bool openMenu = false;
2601 	if (dynamic_cast<BMenuBar*>(this) != NULL)
2602 		openMenu = true;
2603 	_SelectItem(nextItem, openMenu);
2604 	return true;
2605 }
2606 
2607 
2608 BMenuItem*
2609 BMenu::_NextItem(BMenuItem* item, bool forward) const
2610 {
2611 	const int32 numItems = fItems.CountItems();
2612 	if (numItems == 0)
2613 		return NULL;
2614 
2615 	int32 index = fItems.IndexOf(item);
2616 	int32 loopCount = numItems;
2617 	while (--loopCount) {
2618 		// Cycle through menu items in the given direction...
2619 		if (forward)
2620 			index++;
2621 		else
2622 			index--;
2623 
2624 		// ... wrap around...
2625 		if (index < 0)
2626 			index = numItems - 1;
2627 		else if (index >= numItems)
2628 			index = 0;
2629 
2630 		// ... and return the first suitable item found.
2631 		BMenuItem* nextItem = ItemAt(index);
2632 		if (nextItem->IsEnabled())
2633 			return nextItem;
2634 	}
2635 
2636 	// If no other suitable item was found, return NULL.
2637 	return NULL;
2638 }
2639 
2640 
2641 void
2642 BMenu::_SetIgnoreHidden(bool on)
2643 {
2644 	fIgnoreHidden = on;
2645 }
2646 
2647 
2648 void
2649 BMenu::_SetStickyMode(bool on)
2650 {
2651 	if (fStickyMode == on)
2652 		return;
2653 
2654 	fStickyMode = on;
2655 
2656 	// If we are switching to sticky mode, propagate the status
2657 	// back to the super menu
2658 	if (fSuper != NULL)
2659 		fSuper->_SetStickyMode(on);
2660 	else {
2661 		// TODO: Ugly hack, but it needs to be done right here in this method
2662 		BMenuBar* menuBar = dynamic_cast<BMenuBar*>(this);
2663 		if (on && menuBar != NULL && menuBar->LockLooper()) {
2664 			// Steal the focus from the current focus view
2665 			// (needed to handle keyboard navigation)
2666 			menuBar->_StealFocus();
2667 			menuBar->UnlockLooper();
2668 		}
2669 	}
2670 }
2671 
2672 
2673 bool
2674 BMenu::_IsStickyMode() const
2675 {
2676 	return fStickyMode;
2677 }
2678 
2679 
2680 void
2681 BMenu::_GetIsAltCommandKey(bool &value) const
2682 {
2683 	// TODO: Move into init_interface_kit().
2684 	// Currently we can't do that, as get_key_map() blocks forever
2685 	// when called on input_server initialization, since it tries
2686 	// to send a synchronous message to itself (input_server is
2687 	// a BApplication)
2688 
2689 	bool altAsCommand = true;
2690 	key_map* keys = NULL;
2691 	char* chars = NULL;
2692 	get_key_map(&keys, &chars);
2693 	if (keys == NULL || keys->left_command_key != 0x5d
2694 		|| keys->left_control_key != 0x5c)
2695 		altAsCommand = false;
2696 	free(chars);
2697 	free(keys);
2698 
2699 	value = altAsCommand;
2700 }
2701 
2702 
2703 void
2704 BMenu::_CalcTriggers()
2705 {
2706 	BPrivate::TriggerList triggerList;
2707 
2708 	// Gathers the existing triggers set by the user
2709 	for (int32 i = 0; i < CountItems(); i++) {
2710 		char trigger = ItemAt(i)->Trigger();
2711 		if (trigger != 0)
2712 			triggerList.AddTrigger(trigger);
2713 	}
2714 
2715 	// Set triggers for items which don't have one yet
2716 	for (int32 i = 0; i < CountItems(); i++) {
2717 		BMenuItem* item = ItemAt(i);
2718 		if (item->Trigger() == 0) {
2719 			uint32 trigger;
2720 			int32 index;
2721 			if (_ChooseTrigger(item->Label(), index, trigger, triggerList))
2722 				item->SetAutomaticTrigger(index, trigger);
2723 		}
2724 	}
2725 }
2726 
2727 
2728 bool
2729 BMenu::_ChooseTrigger(const char* title, int32& index, uint32& trigger,
2730 	BPrivate::TriggerList& triggers)
2731 {
2732 	if (title == NULL)
2733 		return false;
2734 
2735 	uint32 c;
2736 
2737 	// two runs: first we look out for uppercase letters
2738 	// TODO: support Unicode characters correctly!
2739 	for (uint32 i = 0; (c = title[i]) != '\0'; i++) {
2740 		if (!IsInsideGlyph(c) && isupper(c) && !triggers.HasTrigger(c)) {
2741 			index = i;
2742 			trigger = tolower(c);
2743 			return triggers.AddTrigger(c);
2744 		}
2745 	}
2746 
2747 	// then, if we still haven't found anything, we accept them all
2748 	index = 0;
2749 	while ((c = UTF8ToCharCode(&title)) != 0) {
2750 		if (!isspace(c) && !triggers.HasTrigger(c)) {
2751 			trigger = tolower(c);
2752 			return triggers.AddTrigger(c);
2753 		}
2754 
2755 		index++;
2756 	}
2757 
2758 	return false;
2759 }
2760 
2761 
2762 void
2763 BMenu::_UpdateWindowViewSize(const bool &move)
2764 {
2765 	BMenuWindow* window = static_cast<BMenuWindow*>(Window());
2766 	if (window == NULL)
2767 		return;
2768 
2769 	if (dynamic_cast<BMenuBar*>(this) != NULL)
2770 		return;
2771 
2772 	if (!fResizeToFit)
2773 		return;
2774 
2775 	bool scroll = false;
2776 	const BPoint screenLocation = move ? ScreenLocation() : window->Frame().LeftTop();
2777 	BRect frame = _CalcFrame(screenLocation, &scroll);
2778 	ResizeTo(frame.Width(), frame.Height());
2779 
2780 	if (fItems.CountItems() > 0) {
2781 		if (!scroll) {
2782 			window->ResizeTo(Bounds().Width(), Bounds().Height());
2783 		} else {
2784 			BScreen screen(window);
2785 
2786 			// If we need scrolling, resize the window to fit the screen and
2787 			// attach scrollers to our cached BMenuWindow.
2788 			if (dynamic_cast<BMenuBar*>(Supermenu()) == NULL || frame.top < 0) {
2789 				window->ResizeTo(Bounds().Width(), screen.Frame().Height());
2790 				frame.top = 0;
2791 			} else {
2792 				// Or, in case our parent was a BMenuBar enable scrolling with
2793 				// normal size.
2794 				window->ResizeTo(Bounds().Width(),
2795 					screen.Frame().bottom - frame.top);
2796 			}
2797 
2798 			window->AttachScrollers();
2799 		}
2800 	} else {
2801 		_CacheFontInfo();
2802 		window->ResizeTo(StringWidth(BPrivate::kEmptyMenuLabel)
2803 			+ fPad.left + fPad.right,
2804 			fFontHeight + fPad.top + fPad.bottom);
2805 	}
2806 
2807 	if (move)
2808 		window->MoveTo(frame.LeftTop());
2809 }
2810 
2811 
2812 bool
2813 BMenu::_AddDynamicItems(bool keyDown)
2814 {
2815 	bool addAborted = false;
2816 	if (AddDynamicItem(B_INITIAL_ADD)) {
2817 		BMenuItem* superItem = Superitem();
2818 		BMenu* superMenu = Supermenu();
2819 		do {
2820 			if (superMenu != NULL
2821 				&& !superMenu->_OkToProceed(superItem, keyDown)) {
2822 				AddDynamicItem(B_ABORT);
2823 				addAborted = true;
2824 				break;
2825 			}
2826 		} while (AddDynamicItem(B_PROCESSING));
2827 	}
2828 
2829 	return addAborted;
2830 }
2831 
2832 
2833 bool
2834 BMenu::_OkToProceed(BMenuItem* item, bool keyDown)
2835 {
2836 	BPoint where;
2837 	ulong buttons;
2838 	GetMouse(&where, &buttons, false);
2839 	bool stickyMode = _IsStickyMode();
2840 	// Quit if user clicks the mouse button in sticky mode
2841 	// or releases the mouse button in nonsticky mode
2842 	// or moves the pointer over another item
2843 	// TODO: I added the check for BMenuBar to solve a problem with Deskbar.
2844 	// BeOS seems to do something similar. This could also be a bug in
2845 	// Deskbar, though.
2846 	if ((buttons != 0 && stickyMode)
2847 		|| ((dynamic_cast<BMenuBar*>(this) == NULL
2848 			&& (buttons == 0 && !stickyMode))
2849 		|| ((_HitTestItems(where) != item) && !keyDown)))
2850 		return false;
2851 
2852 	return true;
2853 }
2854 
2855 
2856 bool
2857 BMenu::_CustomTrackingWantsToQuit()
2858 {
2859 	if (fExtraMenuData != NULL && fExtraMenuData->trackingHook != NULL
2860 		&& fExtraMenuData->trackingState != NULL) {
2861 		return fExtraMenuData->trackingHook(this,
2862 			fExtraMenuData->trackingState);
2863 	}
2864 
2865 	return false;
2866 }
2867 
2868 
2869 void
2870 BMenu::_QuitTracking(bool onlyThis)
2871 {
2872 	_SelectItem(NULL);
2873 	if (BMenuBar* menuBar = dynamic_cast<BMenuBar*>(this))
2874 		menuBar->_RestoreFocus();
2875 
2876 	fState = MENU_STATE_CLOSED;
2877 
2878 	// Close the whole menu hierarchy
2879 	if (!onlyThis && _IsStickyMode())
2880 		_SetStickyMode(false);
2881 
2882 	_Hide();
2883 }
2884 
2885 
2886 //	#pragma mark -
2887 
2888 
2889 // TODO: Maybe the following two methods would fit better into
2890 // InterfaceDefs.cpp
2891 // In R5, they do all the work client side, we let the app_server handle the
2892 // details.
2893 status_t
2894 set_menu_info(menu_info* info)
2895 {
2896 	if (!info)
2897 		return B_BAD_VALUE;
2898 
2899 	BPrivate::AppServerLink link;
2900 	link.StartMessage(AS_SET_MENU_INFO);
2901 	link.Attach<menu_info>(*info);
2902 
2903 	status_t status = B_ERROR;
2904 	if (link.FlushWithReply(status) == B_OK && status == B_OK)
2905 		BMenu::sMenuInfo = *info;
2906 		// Update also the local copy, in case anyone relies on it
2907 
2908 	return status;
2909 }
2910 
2911 
2912 status_t
2913 get_menu_info(menu_info* info)
2914 {
2915 	if (!info)
2916 		return B_BAD_VALUE;
2917 
2918 	BPrivate::AppServerLink link;
2919 	link.StartMessage(AS_GET_MENU_INFO);
2920 
2921 	status_t status = B_ERROR;
2922 	if (link.FlushWithReply(status) == B_OK && status == B_OK)
2923 		link.Read<menu_info>(info);
2924 
2925 	return status;
2926 }
2927