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