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