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