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