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