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