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