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