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