xref: /haiku/src/kits/interface/Menu.cpp (revision b289aaf66bbf6e173aa90fa194fc256965f1b34d)
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();
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();
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();
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();
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 (fState != MENU_STATE_CLOSED) {
1659 			bigtime_t snoozeAmount = 50000;
1660 
1661 			BPoint newLocation = location;
1662 			uint32 newButtons = buttons;
1663 
1664 			// If user doesn't move the mouse, loop here,
1665 			// so we don't interfer with keyboard menu navigation
1666 			do {
1667 				snooze(snoozeAmount);
1668 				if (!LockLooper())
1669 					break;
1670 				GetMouse(&newLocation, &newButtons, true);
1671 				UnlockLooper();
1672 			} while (newLocation == location && newButtons == buttons
1673 				&& !(item && item->Submenu() != NULL));
1674 			bigtime_t newPollTime = system_time();
1675 
1676 			// mouseSpeed in px per ms
1677 			// (actually point_distance returns the square of the distance,
1678 			// so it's more px^2 per ms)
1679 			mouseSpeed = (int32)(point_distance(newLocation, location) * 1000
1680 				/ (newPollTime - pollTime));
1681 			pollTime = newPollTime;
1682 
1683 			if (newLocation != location || newButtons != buttons) {
1684 				if (!releasedOnce && newButtons == 0 && buttons != 0)
1685 					releasedOnce = true;
1686 				location = newLocation;
1687 				buttons = newButtons;
1688 			}
1689 
1690 			if (releasedOnce)
1691 				_UpdateStateClose(item, location, buttons);
1692 		}
1693 	}
1694 
1695 	if (action != NULL)
1696 		*action = fState;
1697 
1698 	if (fSelected != NULL && LockLooper()) {
1699 		_SelectItem(NULL);
1700 		UnlockLooper();
1701 	}
1702 
1703 	// delete the menu window recycled for all the child menus
1704 	_DeleteMenuWindow();
1705 
1706 	return item;
1707 }
1708 
1709 
1710 void
1711 BMenu::_UpdateNavigationArea(BPoint position, BRect& navAreaRectAbove,
1712 	BRect& navAreaRectBelow)
1713 {
1714 #define NAV_AREA_THRESHOLD    8
1715 
1716 	// The navigation area is a region in which mouse-overs won't select
1717 	// the item under the cursor. This makes it easier to navigate to
1718 	// submenus, as the cursor can be moved to submenu items directly instead
1719 	// of having to move it horizontally into the submenu first. The concept
1720 	// is illustrated below:
1721 	//
1722 	// +-------+----+---------+
1723 	// |       |   /|         |
1724 	// |       |  /*|         |
1725 	// |[2]--> | /**|         |
1726 	// |       |/[4]|         |
1727 	// |------------|         |
1728 	// |    [1]     |   [6]   |
1729 	// |------------|         |
1730 	// |       |\[5]|         |
1731 	// |[3]--> | \**|         |
1732 	// |       |  \*|         |
1733 	// |       |   \|         |
1734 	// |       +----|---------+
1735 	// |            |
1736 	// +------------+
1737 	//
1738 	// [1] Selected item, cursor position ('position')
1739 	// [2] Upper navigation area rectangle ('navAreaRectAbove')
1740 	// [3] Lower navigation area rectangle ('navAreaRectBelow')
1741 	// [4] Upper navigation area
1742 	// [5] Lower navigation area
1743 	// [6] Submenu
1744 	//
1745 	// The rectangles are used to calculate if the cursor is in the actual
1746 	// navigation area (see _UpdateStateOpenSelect()).
1747 
1748 	if (fSelected == NULL)
1749 		return;
1750 
1751 	BMenu* submenu = fSelected->Submenu();
1752 
1753 	if (submenu != NULL) {
1754 		BRect menuBounds = ConvertToScreen(Bounds());
1755 
1756 		fSelected->Submenu()->LockLooper();
1757 		BRect submenuBounds = fSelected->Submenu()->ConvertToScreen(
1758 			fSelected->Submenu()->Bounds());
1759 		fSelected->Submenu()->UnlockLooper();
1760 
1761 		if (menuBounds.left < submenuBounds.left) {
1762 			navAreaRectAbove.Set(position.x + NAV_AREA_THRESHOLD,
1763 				submenuBounds.top, menuBounds.right,
1764 				position.y);
1765 			navAreaRectBelow.Set(position.x + NAV_AREA_THRESHOLD,
1766 				position.y, menuBounds.right,
1767 				submenuBounds.bottom);
1768 		} else {
1769 			navAreaRectAbove.Set(menuBounds.left,
1770 				submenuBounds.top, position.x - NAV_AREA_THRESHOLD,
1771 				position.y);
1772 			navAreaRectBelow.Set(menuBounds.left,
1773 				position.y, position.x - NAV_AREA_THRESHOLD,
1774 				submenuBounds.bottom);
1775 		}
1776 	} else {
1777 		navAreaRectAbove = BRect();
1778 		navAreaRectBelow = BRect();
1779 	}
1780 }
1781 
1782 
1783 void
1784 BMenu::_UpdateStateOpenSelect(BMenuItem* item, BPoint position,
1785 	BRect& navAreaRectAbove, BRect& navAreaRectBelow, bigtime_t& selectedTime,
1786 	bigtime_t& navigationAreaTime)
1787 {
1788 	if (fState == MENU_STATE_CLOSED)
1789 		return;
1790 
1791 	if (item != fSelected) {
1792 		if (navigationAreaTime == 0)
1793 			navigationAreaTime = system_time();
1794 
1795 		position = ConvertToScreen(position);
1796 
1797 		bool inNavAreaRectAbove = navAreaRectAbove.Contains(position);
1798 		bool inNavAreaRectBelow = navAreaRectBelow.Contains(position);
1799 
1800 		if (!inNavAreaRectAbove && !inNavAreaRectBelow) {
1801 			_SelectItem(item, false);
1802 			navAreaRectAbove = BRect();
1803 			navAreaRectBelow = BRect();
1804 			selectedTime = system_time();
1805 			navigationAreaTime = 0;
1806 			return;
1807 		}
1808 
1809 		BRect menuBounds = ConvertToScreen(Bounds());
1810 
1811 		fSelected->Submenu()->LockLooper();
1812 		BRect submenuBounds = fSelected->Submenu()->ConvertToScreen(
1813 			fSelected->Submenu()->Bounds());
1814 		fSelected->Submenu()->UnlockLooper();
1815 
1816 		float xOffset;
1817 
1818 		// navAreaRectAbove and navAreaRectBelow have the same X
1819 		// position and width, so it doesn't matter which one we use to
1820 		// calculate the X offset
1821 		if (menuBounds.left < submenuBounds.left)
1822 			xOffset = position.x - navAreaRectAbove.left;
1823 		else
1824 			xOffset = navAreaRectAbove.right - position.x;
1825 
1826 		bool inNavArea;
1827 
1828 		if (inNavAreaRectAbove) {
1829 			float yOffset = navAreaRectAbove.bottom - position.y;
1830 			float ratio = navAreaRectAbove.Width() / navAreaRectAbove.Height();
1831 
1832 			inNavArea = yOffset <= xOffset / ratio;
1833 		} else {
1834 			float yOffset = navAreaRectBelow.bottom - position.y;
1835 			float ratio = navAreaRectBelow.Width() / navAreaRectBelow.Height();
1836 
1837 			inNavArea = yOffset >= (navAreaRectBelow.Height() - xOffset
1838 				/ ratio);
1839 		}
1840 
1841 		bigtime_t systime = system_time();
1842 
1843 		if (!inNavArea || (navigationAreaTime > 0 && systime -
1844 			navigationAreaTime > kNavigationAreaTimeout)) {
1845 			// Don't delay opening of submenu if the user had
1846 			// to wait for the navigation area timeout anyway
1847 			_SelectItem(item, inNavArea);
1848 
1849 			if (inNavArea) {
1850 				_UpdateNavigationArea(position, navAreaRectAbove,
1851 					navAreaRectBelow);
1852 			} else {
1853 				navAreaRectAbove = BRect();
1854 				navAreaRectBelow = BRect();
1855 			}
1856 
1857 			selectedTime = system_time();
1858 			navigationAreaTime = 0;
1859 		}
1860 	} else if (fSelected->Submenu() != NULL &&
1861 		system_time() - selectedTime > kOpenSubmenuDelay) {
1862 		_SelectItem(fSelected, true);
1863 
1864 		if (!navAreaRectAbove.IsValid() && !navAreaRectBelow.IsValid()) {
1865 			position = ConvertToScreen(position);
1866 			_UpdateNavigationArea(position, navAreaRectAbove,
1867 				navAreaRectBelow);
1868 		}
1869 	}
1870 
1871 	if (fState != MENU_STATE_TRACKING)
1872 		fState = MENU_STATE_TRACKING;
1873 }
1874 
1875 
1876 void
1877 BMenu::_UpdateStateClose(BMenuItem* item, const BPoint& where,
1878 	const uint32& buttons)
1879 {
1880 	if (fState == MENU_STATE_CLOSED)
1881 		return;
1882 
1883 	if (buttons != 0 && _IsStickyMode()) {
1884 		if (item == NULL) {
1885 			if (item != fSelected) {
1886 				LockLooper();
1887 				_SelectItem(item, false);
1888 				UnlockLooper();
1889 			}
1890 			fState = MENU_STATE_CLOSED;
1891 		} else
1892 			_SetStickyMode(false);
1893 	} else if (buttons == 0 && !_IsStickyMode()) {
1894 		if (fExtraRect != NULL && fExtraRect->Contains(where)) {
1895 			_SetStickyMode(true);
1896 			fExtraRect = NULL;
1897 				// Setting this to NULL will prevent this code
1898 				// to be executed next time
1899 		} else {
1900 			if (item != fSelected) {
1901 				LockLooper();
1902 				_SelectItem(item, false);
1903 				UnlockLooper();
1904 			}
1905 			fState = MENU_STATE_CLOSED;
1906 		}
1907 	}
1908 }
1909 
1910 
1911 bool
1912 BMenu::_AddItem(BMenuItem* item, int32 index)
1913 {
1914 	ASSERT(item != NULL);
1915 	if (index < 0 || index > fItems.CountItems())
1916 		return false;
1917 
1918 	if (item->IsMarked())
1919 		_ItemMarked(item);
1920 
1921 	if (!fItems.AddItem(item, index))
1922 		return false;
1923 
1924 	// install the item on the supermenu's window
1925 	// or onto our window, if we are a root menu
1926 	BWindow* window = NULL;
1927 	if (Superitem() != NULL)
1928 		window = Superitem()->fWindow;
1929 	else
1930 		window = Window();
1931 	if (window != NULL)
1932 		item->Install(window);
1933 
1934 	item->SetSuper(this);
1935 	return true;
1936 }
1937 
1938 
1939 bool
1940 BMenu::_RemoveItems(int32 index, int32 count, BMenuItem* item,
1941 	bool deleteItems)
1942 {
1943 	bool success = false;
1944 	bool invalidateLayout = false;
1945 
1946 	bool locked = LockLooper();
1947 	BWindow* window = Window();
1948 
1949 	// The plan is simple: If we're given a BMenuItem directly, we use it
1950 	// and ignore index and count. Otherwise, we use them instead.
1951 	if (item != NULL) {
1952 		if (fItems.RemoveItem(item)) {
1953 			if (item == fSelected && window != NULL)
1954 				_SelectItem(NULL);
1955 			item->Uninstall();
1956 			item->SetSuper(NULL);
1957 			if (deleteItems)
1958 				delete item;
1959 			success = invalidateLayout = true;
1960 		}
1961 	} else {
1962 		// We iterate backwards because it's simpler
1963 		int32 i = min_c(index + count - 1, fItems.CountItems() - 1);
1964 		// NOTE: the range check for "index" is done after
1965 		// calculating the last index to be removed, so
1966 		// that the range is not "shifted" unintentionally
1967 		index = max_c(0, index);
1968 		for (; i >= index; i--) {
1969 			item = static_cast<BMenuItem*>(fItems.ItemAt(i));
1970 			if (item != NULL) {
1971 				if (fItems.RemoveItem(item)) {
1972 					if (item == fSelected && window != NULL)
1973 						_SelectItem(NULL);
1974 					item->Uninstall();
1975 					item->SetSuper(NULL);
1976 					if (deleteItems)
1977 						delete item;
1978 					success = true;
1979 					invalidateLayout = true;
1980 				} else {
1981 					// operation not entirely successful
1982 					success = false;
1983 					break;
1984 				}
1985 			}
1986 		}
1987 	}
1988 
1989 	if (invalidateLayout) {
1990 		InvalidateLayout();
1991 		if (locked && window != NULL) {
1992 			_LayoutItems(0);
1993 			_UpdateWindowViewSize();
1994 			Invalidate();
1995 		}
1996 	}
1997 
1998 	if (locked)
1999 		UnlockLooper();
2000 
2001 	return success;
2002 }
2003 
2004 
2005 bool
2006 BMenu::_RelayoutIfNeeded()
2007 {
2008 	if (!fUseCachedMenuLayout) {
2009 		fUseCachedMenuLayout = true;
2010 		_CacheFontInfo();
2011 		_LayoutItems(0);
2012 		return true;
2013 	}
2014 	return false;
2015 }
2016 
2017 
2018 void
2019 BMenu::_LayoutItems(int32 index)
2020 {
2021 	_CalcTriggers();
2022 
2023 	float width, height;
2024 	_ComputeLayout(index, fResizeToFit, true, &width, &height);
2025 
2026 	if (fResizeToFit)
2027 		ResizeTo(width, height);
2028 }
2029 
2030 
2031 BSize
2032 BMenu::_ValidatePreferredSize()
2033 {
2034 	if (!fLayoutData->preferred.IsWidthSet() || ResizingMode()
2035 			!= fLayoutData->lastResizingMode) {
2036 		_ComputeLayout(0, true, false, NULL, NULL);
2037 	}
2038 
2039 	return fLayoutData->preferred;
2040 }
2041 
2042 
2043 void
2044 BMenu::_ComputeLayout(int32 index, bool bestFit, bool moveItems,
2045 	float* _width, float* _height)
2046 {
2047 	// TODO: Take "bestFit", "moveItems", "index" into account,
2048 	// Recalculate only the needed items,
2049 	// not the whole layout every time
2050 
2051 	fLayoutData->lastResizingMode = ResizingMode();
2052 
2053 	BRect frame;
2054 
2055 	switch (fLayout) {
2056 		case B_ITEMS_IN_COLUMN:
2057 			_ComputeColumnLayout(index, bestFit, moveItems, frame);
2058 			break;
2059 
2060 		case B_ITEMS_IN_ROW:
2061 			_ComputeRowLayout(index, bestFit, moveItems, frame);
2062 			break;
2063 
2064 		case B_ITEMS_IN_MATRIX:
2065 			_ComputeMatrixLayout(frame);
2066 			break;
2067 
2068 		default:
2069 			break;
2070 	}
2071 
2072 	// change width depending on resize mode
2073 	BSize size;
2074 	if ((ResizingMode() & B_FOLLOW_LEFT_RIGHT) == B_FOLLOW_LEFT_RIGHT) {
2075 		if (Parent())
2076 			size.width = Parent()->Frame().Width() + 1;
2077 		else if (Window())
2078 			size.width = Window()->Frame().Width() + 1;
2079 		else
2080 			size.width = Bounds().Width();
2081 	} else
2082 		size.width = frame.Width();
2083 
2084 	size.height = frame.Height();
2085 
2086 	if (_width)
2087 		*_width = size.width;
2088 
2089 	if (_height)
2090 		*_height = size.height;
2091 
2092 	if (bestFit)
2093 		fLayoutData->preferred = size;
2094 
2095 	if (moveItems)
2096 		fUseCachedMenuLayout = true;
2097 }
2098 
2099 
2100 void
2101 BMenu::_ComputeColumnLayout(int32 index, bool bestFit, bool moveItems,
2102 	BRect& frame)
2103 {
2104 	BFont font;
2105 	GetFont(&font);
2106 	bool command = false;
2107 	bool control = false;
2108 	bool shift = false;
2109 	bool option = false;
2110 	if (index > 0)
2111 		frame = ItemAt(index - 1)->Frame();
2112 	else
2113 		frame.Set(0, 0, 0, -1);
2114 
2115 	for (; index < fItems.CountItems(); index++) {
2116 		BMenuItem* item = ItemAt(index);
2117 
2118 		float width, height;
2119 		item->GetContentSize(&width, &height);
2120 
2121 		if (item->fModifiers && item->fShortcutChar) {
2122 			width += font.Size();
2123 			if (item->fModifiers & B_COMMAND_KEY)
2124 				command = true;
2125 			if (item->fModifiers & B_CONTROL_KEY)
2126 				control = true;
2127 			if (item->fModifiers & B_SHIFT_KEY)
2128 				shift = true;
2129 			if (item->fModifiers & B_OPTION_KEY)
2130 				option = true;
2131 		}
2132 
2133 		item->fBounds.left = 0.0f;
2134 		item->fBounds.top = frame.bottom + 1.0f;
2135 		item->fBounds.bottom = item->fBounds.top + height + fPad.top
2136 			+ fPad.bottom;
2137 
2138 		if (item->fSubmenu != NULL)
2139 			width += item->Frame().Height();
2140 
2141 		frame.right = max_c(frame.right, width + fPad.left + fPad.right);
2142 		frame.bottom = item->fBounds.bottom;
2143 	}
2144 
2145 	if (command)
2146 		frame.right += BPrivate::MenuPrivate::MenuItemCommand()->Bounds().Width() + 1;
2147 	if (control)
2148 		frame.right += BPrivate::MenuPrivate::MenuItemControl()->Bounds().Width() + 1;
2149 	if (option)
2150 		frame.right += BPrivate::MenuPrivate::MenuItemOption()->Bounds().Width() + 1;
2151 	if (shift)
2152 		frame.right += BPrivate::MenuPrivate::MenuItemShift()->Bounds().Width() + 1;
2153 
2154 	if (fMaxContentWidth > 0)
2155 		frame.right = min_c(frame.right, fMaxContentWidth);
2156 
2157 	if (moveItems) {
2158 		for (int32 i = 0; i < fItems.CountItems(); i++)
2159 			ItemAt(i)->fBounds.right = frame.right;
2160 	}
2161 
2162 	frame.top = 0;
2163 	frame.right = ceilf(frame.right);
2164 }
2165 
2166 
2167 void
2168 BMenu::_ComputeRowLayout(int32 index, bool bestFit, bool moveItems,
2169 	BRect& frame)
2170 {
2171 	font_height fh;
2172 	GetFontHeight(&fh);
2173 	frame.Set(0.0f, 0.0f, 0.0f, ceilf(fh.ascent + fh.descent + fPad.top
2174 		+ fPad.bottom));
2175 
2176 	for (int32 i = 0; i < fItems.CountItems(); i++) {
2177 		BMenuItem* item = ItemAt(i);
2178 
2179 		float width, height;
2180 		item->GetContentSize(&width, &height);
2181 
2182 		item->fBounds.left = frame.right;
2183 		item->fBounds.top = 0.0f;
2184 		item->fBounds.right = item->fBounds.left + width + fPad.left
2185 			+ fPad.right;
2186 
2187 		frame.right = item->Frame().right + 1.0f;
2188 		frame.bottom = max_c(frame.bottom, height + fPad.top + fPad.bottom);
2189 	}
2190 
2191 	if (moveItems) {
2192 		for (int32 i = 0; i < fItems.CountItems(); i++)
2193 			ItemAt(i)->fBounds.bottom = frame.bottom;
2194 	}
2195 
2196 	if (bestFit)
2197 		frame.right = ceilf(frame.right);
2198 	else
2199 		frame.right = Bounds().right;
2200 }
2201 
2202 
2203 void
2204 BMenu::_ComputeMatrixLayout(BRect &frame)
2205 {
2206 	frame.Set(0, 0, 0, 0);
2207 	for (int32 i = 0; i < CountItems(); i++) {
2208 		BMenuItem* item = ItemAt(i);
2209 		if (item != NULL) {
2210 			frame.left = min_c(frame.left, item->Frame().left);
2211 			frame.right = max_c(frame.right, item->Frame().right);
2212 			frame.top = min_c(frame.top, item->Frame().top);
2213 			frame.bottom = max_c(frame.bottom, item->Frame().bottom);
2214 		}
2215 	}
2216 }
2217 
2218 
2219 // Assumes the SuperMenu to be locked (due to calling ConvertToScreen())
2220 BPoint
2221 BMenu::ScreenLocation()
2222 {
2223 	BMenu* superMenu = Supermenu();
2224 	BMenuItem* superItem = Superitem();
2225 
2226 	if (superMenu == NULL || superItem == NULL) {
2227 		debugger("BMenu can't determine where to draw."
2228 			"Override BMenu::ScreenLocation() to determine location.");
2229 	}
2230 
2231 	BPoint point;
2232 	if (superMenu->Layout() == B_ITEMS_IN_COLUMN)
2233 		point = superItem->Frame().RightTop() + BPoint(1.0f, 1.0f);
2234 	else
2235 		point = superItem->Frame().LeftBottom() + BPoint(1.0f, 1.0f);
2236 
2237 	superMenu->ConvertToScreen(&point);
2238 
2239 	return point;
2240 }
2241 
2242 
2243 BRect
2244 BMenu::_CalcFrame(BPoint where, bool* scrollOn)
2245 {
2246 	// TODO: Improve me
2247 	BRect bounds = Bounds();
2248 	BRect frame = bounds.OffsetToCopy(where);
2249 
2250 	BScreen screen(Window());
2251 	BRect screenFrame = screen.Frame();
2252 
2253 	BMenu* superMenu = Supermenu();
2254 	BMenuItem* superItem = Superitem();
2255 
2256 	bool scroll = false;
2257 
2258 	// TODO: Horrible hack:
2259 	// When added to a BMenuField, a BPopUpMenu is the child of
2260 	// a _BMCMenuBar_ to "fake" the menu hierarchy
2261 	if (superMenu == NULL || superItem == NULL
2262 		|| dynamic_cast<_BMCMenuBar_*>(superMenu) != NULL) {
2263 		// just move the window on screen
2264 
2265 		if (frame.bottom > screenFrame.bottom)
2266 			frame.OffsetBy(0, screenFrame.bottom - frame.bottom);
2267 		else if (frame.top < screenFrame.top)
2268 			frame.OffsetBy(0, -frame.top);
2269 
2270 		if (frame.right > screenFrame.right)
2271 			frame.OffsetBy(screenFrame.right - frame.right, 0);
2272 		else if (frame.left < screenFrame.left)
2273 			frame.OffsetBy(-frame.left, 0);
2274 	} else if (superMenu->Layout() == B_ITEMS_IN_COLUMN) {
2275 		if (frame.right > screenFrame.right)
2276 			frame.OffsetBy(-superItem->Frame().Width() - frame.Width() - 2, 0);
2277 
2278 		if (frame.left < 0)
2279 			frame.OffsetBy(-frame.left + 6, 0);
2280 
2281 		if (frame.bottom > screenFrame.bottom)
2282 			frame.OffsetBy(0, screenFrame.bottom - frame.bottom);
2283 	} else {
2284 		if (frame.bottom > screenFrame.bottom) {
2285 			if (scrollOn != NULL && superMenu != NULL
2286 				&& dynamic_cast<BMenuBar*>(superMenu) != NULL
2287 				&& frame.top < (screenFrame.bottom - 80)) {
2288 				scroll = true;
2289 			} else {
2290 				frame.OffsetBy(0, -superItem->Frame().Height()
2291 					- frame.Height() - 3);
2292 			}
2293 		}
2294 
2295 		if (frame.right > screenFrame.right)
2296 			frame.OffsetBy(screenFrame.right - frame.right, 0);
2297 	}
2298 
2299 	if (!scroll) {
2300 		// basically, if this returns false, it means
2301 		// that the menu frame won't fit completely inside the screen
2302 		// TODO: Scrolling, will currently only work up/down,
2303 		// not left/right
2304 		scroll = screenFrame.Height() < frame.Height();
2305 	}
2306 
2307 	if (scrollOn != NULL)
2308 		*scrollOn = scroll;
2309 
2310 	return frame;
2311 }
2312 
2313 
2314 void
2315 BMenu::_DrawItems(BRect updateRect)
2316 {
2317 	int32 itemCount = fItems.CountItems();
2318 	for (int32 i = 0; i < itemCount; i++) {
2319 		BMenuItem* item = ItemAt(i);
2320 		if (item->Frame().Intersects(updateRect))
2321 			item->Draw();
2322 	}
2323 }
2324 
2325 
2326 int
2327 BMenu::_State(BMenuItem** item) const
2328 {
2329 	if (fState == MENU_STATE_TRACKING || fState == MENU_STATE_CLOSED)
2330 		return fState;
2331 
2332 	if (fSelected != NULL && fSelected->Submenu() != NULL)
2333 		return fSelected->Submenu()->_State(item);
2334 
2335 	return fState;
2336 }
2337 
2338 
2339 void
2340 BMenu::_InvokeItem(BMenuItem* item, bool now)
2341 {
2342 	if (!item->IsEnabled())
2343 		return;
2344 
2345 	// Do the "selected" animation
2346 	// TODO: Doesn't work. This is supposed to highlight
2347 	// and dehighlight the item, works on beos but not on haiku.
2348 	if (!item->Submenu() && LockLooper()) {
2349 		snooze(50000);
2350 		item->Select(true);
2351 		Window()->UpdateIfNeeded();
2352 		snooze(50000);
2353 		item->Select(false);
2354 		Window()->UpdateIfNeeded();
2355 		snooze(50000);
2356 		item->Select(true);
2357 		Window()->UpdateIfNeeded();
2358 		snooze(50000);
2359 		item->Select(false);
2360 		Window()->UpdateIfNeeded();
2361 		UnlockLooper();
2362 	}
2363 
2364 	// Lock the root menu window before calling BMenuItem::Invoke()
2365 	BMenu* parent = this;
2366 	BMenu* rootMenu = NULL;
2367 	do {
2368 		rootMenu = parent;
2369 		parent = rootMenu->Supermenu();
2370 	} while (parent != NULL);
2371 
2372 	if (rootMenu->LockLooper()) {
2373 		item->Invoke();
2374 		rootMenu->UnlockLooper();
2375 	}
2376 }
2377 
2378 
2379 bool
2380 BMenu::_OverSuper(BPoint location)
2381 {
2382 	if (!Supermenu())
2383 		return false;
2384 
2385 	return fSuperbounds.Contains(location);
2386 }
2387 
2388 
2389 bool
2390 BMenu::_OverSubmenu(BMenuItem* item, BPoint loc)
2391 {
2392 	if (item == NULL)
2393 		return false;
2394 
2395 	BMenu* subMenu = item->Submenu();
2396 	if (subMenu == NULL || subMenu->Window() == NULL)
2397 		return false;
2398 
2399 	// we assume that loc is in screen coords {
2400 	if (subMenu->Window()->Frame().Contains(loc))
2401 		return true;
2402 
2403 	return subMenu->_OverSubmenu(subMenu->fSelected, loc);
2404 }
2405 
2406 
2407 BMenuWindow*
2408 BMenu::_MenuWindow()
2409 {
2410 #if USE_CACHED_MENUWINDOW
2411 	if (fCachedMenuWindow == NULL) {
2412 		char windowName[64];
2413 		snprintf(windowName, 64, "%s cached menu", Name());
2414 		fCachedMenuWindow = new (nothrow) BMenuWindow(windowName);
2415 	}
2416 #endif
2417 	return fCachedMenuWindow;
2418 }
2419 
2420 
2421 void
2422 BMenu::_DeleteMenuWindow()
2423 {
2424 	if (fCachedMenuWindow != NULL) {
2425 		fCachedMenuWindow->Lock();
2426 		fCachedMenuWindow->Quit();
2427 		fCachedMenuWindow = NULL;
2428 	}
2429 }
2430 
2431 
2432 BMenuItem*
2433 BMenu::_HitTestItems(BPoint where, BPoint slop) const
2434 {
2435 	// TODO: Take "slop" into account ?
2436 
2437 	// if the point doesn't lie within the menu's
2438 	// bounds, bail out immediately
2439 	if (!Bounds().Contains(where))
2440 		return NULL;
2441 
2442 	int32 itemCount = CountItems();
2443 	for (int32 i = 0; i < itemCount; i++) {
2444 		BMenuItem* item = ItemAt(i);
2445 		if (item->IsEnabled() && item->Frame().Contains(where))
2446 			return item;
2447 	}
2448 
2449 	return NULL;
2450 }
2451 
2452 
2453 BRect
2454 BMenu::_Superbounds() const
2455 {
2456 	return fSuperbounds;
2457 }
2458 
2459 
2460 void
2461 BMenu::_CacheFontInfo()
2462 {
2463 	font_height fh;
2464 	GetFontHeight(&fh);
2465 	fAscent = fh.ascent;
2466 	fDescent = fh.descent;
2467 	fFontHeight = ceilf(fh.ascent + fh.descent + fh.leading);
2468 }
2469 
2470 
2471 void
2472 BMenu::_ItemMarked(BMenuItem* item)
2473 {
2474 	if (IsRadioMode()) {
2475 		for (int32 i = 0; i < CountItems(); i++) {
2476 			if (ItemAt(i) != item)
2477 				ItemAt(i)->SetMarked(false);
2478 		}
2479 		InvalidateLayout();
2480 	}
2481 
2482 	if (IsLabelFromMarked() && Superitem())
2483 		Superitem()->SetLabel(item->Label());
2484 }
2485 
2486 
2487 void
2488 BMenu::_Install(BWindow* target)
2489 {
2490 	for (int32 i = 0; i < CountItems(); i++)
2491 		ItemAt(i)->Install(target);
2492 }
2493 
2494 
2495 void
2496 BMenu::_Uninstall()
2497 {
2498 	for (int32 i = 0; i < CountItems(); i++)
2499 		ItemAt(i)->Uninstall();
2500 }
2501 
2502 
2503 void
2504 BMenu::_SelectItem(BMenuItem* menuItem, bool showSubmenu, bool selectFirstItem)
2505 {
2506 	// Avoid deselecting and then reselecting the same item
2507 	// which would cause flickering
2508 	if (menuItem != fSelected) {
2509 		if (fSelected != NULL) {
2510 			fSelected->Select(false);
2511 			BMenu* subMenu = fSelected->Submenu();
2512 			if (subMenu != NULL && subMenu->Window() != NULL)
2513 				subMenu->_Hide();
2514 		}
2515 
2516 		fSelected = menuItem;
2517 		if (fSelected != NULL)
2518 			fSelected->Select(true);
2519 	}
2520 
2521 	if (fSelected != NULL && showSubmenu) {
2522 		BMenu* subMenu = fSelected->Submenu();
2523 		if (subMenu != NULL && subMenu->Window() == NULL) {
2524 			if (!subMenu->_Show(selectFirstItem)) {
2525 				// something went wrong, deselect the item
2526 				fSelected->Select(false);
2527 				fSelected = NULL;
2528 			}
2529 		}
2530 	}
2531 }
2532 
2533 
2534 bool
2535 BMenu::_SelectNextItem(BMenuItem* item, bool forward)
2536 {
2537 	if (CountItems() == 0) // cannot select next item in an empty menu
2538 		return false;
2539 
2540 	BMenuItem* nextItem = _NextItem(item, forward);
2541 	if (nextItem == NULL)
2542 		return false;
2543 
2544 	bool openMenu = false;
2545 	if (dynamic_cast<BMenuBar*>(this) != NULL)
2546 		openMenu = true;
2547 	_SelectItem(nextItem, openMenu);
2548 	return true;
2549 }
2550 
2551 
2552 BMenuItem*
2553 BMenu::_NextItem(BMenuItem* item, bool forward) const
2554 {
2555 	// go to next item, and skip over disabled items such as separators
2556 	int32 index = fItems.IndexOf(item);
2557 	const int32 numItems = fItems.CountItems();
2558 	if (index < 0) {
2559 		if (forward)
2560 			index = -1;
2561 		else
2562 			index = numItems;
2563 	}
2564 	int32 startIndex = index;
2565 	do {
2566 		if (forward)
2567 			index++;
2568 		else
2569 			index--;
2570 
2571 		// cycle through menu items
2572 		if (index < 0)
2573 			index = numItems - 1;
2574 		else if (index >= numItems)
2575 			index = 0;
2576 	} while (!ItemAt(index)->IsEnabled() && index != startIndex);
2577 
2578 	if (index == startIndex) {
2579 		// We are back where we started and no item was enabled.
2580 		return NULL;
2581 	}
2582 
2583 	return ItemAt(index);
2584 }
2585 
2586 
2587 void
2588 BMenu::_SetIgnoreHidden(bool on)
2589 {
2590 	fIgnoreHidden = on;
2591 }
2592 
2593 
2594 void
2595 BMenu::_SetStickyMode(bool on)
2596 {
2597 	if (fStickyMode == on)
2598 		return;
2599 
2600 	fStickyMode = on;
2601 
2602 	// If we are switching to sticky mode, propagate the status
2603 	// back to the super menu
2604 	if (fSuper != NULL)
2605 		fSuper->_SetStickyMode(on);
2606 	else {
2607 		// TODO: Ugly hack, but it needs to be done right here in this method
2608 		BMenuBar* menuBar = dynamic_cast<BMenuBar*>(this);
2609 		if (on && menuBar != NULL && menuBar->LockLooper()) {
2610 			// Steal the focus from the current focus view
2611 			// (needed to handle keyboard navigation)
2612 			menuBar->_StealFocus();
2613 			menuBar->UnlockLooper();
2614 		}
2615 	}
2616 }
2617 
2618 
2619 bool
2620 BMenu::_IsStickyMode() const
2621 {
2622 	return fStickyMode;
2623 }
2624 
2625 
2626 void
2627 BMenu::_CalcTriggers()
2628 {
2629 	BPrivate::TriggerList triggerList;
2630 
2631 	// Gathers the existing triggers set by the user
2632 	for (int32 i = 0; i < CountItems(); i++) {
2633 		char trigger = ItemAt(i)->Trigger();
2634 		if (trigger != 0)
2635 			triggerList.AddTrigger(trigger);
2636 	}
2637 
2638 	// Set triggers for items which don't have one yet
2639 	for (int32 i = 0; i < CountItems(); i++) {
2640 		BMenuItem* item = ItemAt(i);
2641 		if (item->Trigger() == 0) {
2642 			uint32 trigger;
2643 			int32 index;
2644 			if (_ChooseTrigger(item->Label(), index, trigger, triggerList))
2645 				item->SetAutomaticTrigger(index, trigger);
2646 		}
2647 	}
2648 }
2649 
2650 
2651 bool
2652 BMenu::_ChooseTrigger(const char* title, int32& index, uint32& trigger,
2653 	BPrivate::TriggerList& triggers)
2654 {
2655 	if (title == NULL)
2656 		return false;
2657 
2658 	uint32 c;
2659 
2660 	// two runs: first we look out for uppercase letters
2661 	// TODO: support Unicode characters correctly!
2662 	for (uint32 i = 0; (c = title[i]) != '\0'; i++) {
2663 		if (!IsInsideGlyph(c) && isupper(c) && !triggers.HasTrigger(c)) {
2664 			index = i;
2665 			trigger = tolower(c);
2666 			return triggers.AddTrigger(c);
2667 		}
2668 	}
2669 
2670 	// then, if we still haven't found anything, we accept them all
2671 	index = 0;
2672 	while ((c = UTF8ToCharCode(&title)) != 0) {
2673 		if (!isspace(c) && !triggers.HasTrigger(c)) {
2674 			trigger = tolower(c);
2675 			return triggers.AddTrigger(c);
2676 		}
2677 
2678 		index++;
2679 	}
2680 
2681 	return false;
2682 }
2683 
2684 
2685 void
2686 BMenu::_UpdateWindowViewSize()
2687 {
2688 	BMenuWindow* window = static_cast<BMenuWindow*>(Window());
2689 	if (window == NULL)
2690 		return;
2691 
2692 	if (dynamic_cast<BMenuBar*>(this) != NULL)
2693 		return;
2694 
2695 	if (!fResizeToFit)
2696 		return;
2697 
2698 	bool scroll = false;
2699 	const BPoint screenLocation = ScreenLocation();
2700 	BRect frame = _CalcFrame(screenLocation, &scroll);
2701 	ResizeTo(frame.Width(), frame.Height());
2702 
2703 	if (fItems.CountItems() > 0) {
2704 		if (!scroll) {
2705 			window->ResizeTo(Bounds().Width(), Bounds().Height());
2706 		} else {
2707 			BScreen screen(window);
2708 
2709 			// If we need scrolling, resize the window to fit the screen and
2710 			// attach scrollers to our cached BMenuWindow.
2711 			if (dynamic_cast<BMenuBar*>(Supermenu()) == NULL || frame.top < 0) {
2712 				window->ResizeTo(Bounds().Width(), screen.Frame().Height());
2713 				frame.top = 0;
2714 			} else {
2715 				// Or, in case our parent was a BMenuBar enable scrolling with
2716 				// normal size.
2717 				window->ResizeTo(Bounds().Width(),
2718 					screen.Frame().bottom - frame.top);
2719 			}
2720 
2721 			window->AttachScrollers();
2722 		}
2723 	} else {
2724 		_CacheFontInfo();
2725 		window->ResizeTo(StringWidth(BPrivate::kEmptyMenuLabel)
2726 			+ fPad.left + fPad.right,
2727 			fFontHeight + fPad.top + fPad.bottom);
2728 	}
2729 
2730 	window->MoveTo(frame.LeftTop());
2731 }
2732 
2733 
2734 bool
2735 BMenu::_OkToProceed(BMenuItem* item)
2736 {
2737 	BPoint where;
2738 	ulong buttons;
2739 	GetMouse(&where, &buttons, false);
2740 	bool stickyMode = _IsStickyMode();
2741 	// Quit if user clicks the mouse button in sticky mode
2742 	// or releases the mouse button in nonsticky mode
2743 	// or moves the pointer over another item
2744 	// TODO: I added the check for BMenuBar to solve a problem with Deskbar.
2745 	// BeOS seems to do something similar. This could also be a bug in
2746 	// Deskbar, though.
2747 	if ((buttons != 0 && stickyMode)
2748 		|| ((dynamic_cast<BMenuBar*>(this) == NULL
2749 			&& (buttons == 0 && !stickyMode)) || _HitTestItems(where) != item))
2750 		return false;
2751 
2752 	return true;
2753 }
2754 
2755 
2756 bool
2757 BMenu::_CustomTrackingWantsToQuit()
2758 {
2759 	if (fExtraMenuData != NULL && fExtraMenuData->trackingHook != NULL
2760 		&& fExtraMenuData->trackingState != NULL) {
2761 		return fExtraMenuData->trackingHook(this,
2762 			fExtraMenuData->trackingState);
2763 	}
2764 
2765 	return false;
2766 }
2767 
2768 
2769 void
2770 BMenu::_QuitTracking(bool onlyThis)
2771 {
2772 	_SelectItem(NULL);
2773 	if (BMenuBar* menuBar = dynamic_cast<BMenuBar*>(this))
2774 		menuBar->_RestoreFocus();
2775 
2776 	fChosenItem = NULL;
2777 	fState = MENU_STATE_CLOSED;
2778 
2779 	// Close the whole menu hierarchy
2780 	if (!onlyThis && _IsStickyMode())
2781 		_SetStickyMode(false);
2782 
2783 	_Hide();
2784 }
2785 
2786 
2787 //	#pragma mark -
2788 
2789 
2790 // TODO: Maybe the following two methods would fit better into
2791 // InterfaceDefs.cpp
2792 // In R5, they do all the work client side, we let the app_server handle the
2793 // details.
2794 status_t
2795 set_menu_info(menu_info* info)
2796 {
2797 	if (!info)
2798 		return B_BAD_VALUE;
2799 
2800 	BPrivate::AppServerLink link;
2801 	link.StartMessage(AS_SET_MENU_INFO);
2802 	link.Attach<menu_info>(*info);
2803 
2804 	status_t status = B_ERROR;
2805 	if (link.FlushWithReply(status) == B_OK && status == B_OK)
2806 		BMenu::sMenuInfo = *info;
2807 		// Update also the local copy, in case anyone relies on it
2808 
2809 	return status;
2810 }
2811 
2812 
2813 status_t
2814 get_menu_info(menu_info* info)
2815 {
2816 	if (!info)
2817 		return B_BAD_VALUE;
2818 
2819 	BPrivate::AppServerLink link;
2820 	link.StartMessage(AS_GET_MENU_INFO);
2821 
2822 	status_t status = B_ERROR;
2823 	if (link.FlushWithReply(status) == B_OK && status == B_OK)
2824 		link.Read<menu_info>(info);
2825 
2826 	return status;
2827 }
2828