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