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