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