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