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