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